关于c#:如何防止工厂方法模式在构造函数中引发有关虚拟成员调用的警告?

How to prevent Factory Method pattern causing warning about virtual member call in constructor?

在www.dofactory.com上,我找到了工厂模式的真实例子。但代码在resharper中生成一个关于构造函数中虚拟成员调用的警告。

导致警告的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
abstract class Document
{
    private List<Page> _pages = new List<Page>();

    // Constructor calls abstract Factory method
    public Document()
    {
        this.CreatePages(); // <= this line is causing the warning
    }

    public List<Page> Pages
    {
        get { return _pages; }
    }

    // Factory Method
    public abstract void CreatePages();
}

class Resume : Document
{
    // Factory Method implementation
    public override void CreatePages()
    {
        Pages.Add(new SkillsPage());
        Pages.Add(new EducationPage());
        Pages.Add(new ExperiencePage());
    }
}

在消费代码中,您可以简单地使用:

1
Document document = new Resume();

我确实理解为什么在构造函数中调用虚拟成员是一个坏主意(如这里所解释的)。

我的问题是,如何重构这一点,以便仍然使用工厂模式,但不需要在构造函数中调用虚拟成员。

如果我只是从构造函数中移除对CreatePages的调用,那么使用者必须显式调用CreatePages方法:

1
2
Document document = new Resume();
document.CreatePages();

我更喜欢这样的情况,即创建一个新的Resume是实际创建包含页面的简历所需要的全部。


重构的一种方法是预先传递页面,然后将它们传递给受保护的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Document {
    protected Document(IEnumerable<Page> pages) {
        // If it's OK to add to _pages, do not use AsReadOnly
        _pages = pages.ToList().AsReadOnly();
    }
    // ...
}

public class Resume : Document {
    public Resume() : base(CreatePages()) {
    }
    private static IEnumerable<Page> CreatePages() {
        return new Page[] {
            new SkillsPage(),
            new EducationPage(),
            new ExperiencePage()
        };
    }
}

另外,我不知道这和工厂方法有什么关系。您的文章说明了模板方法模式。


My question is how you can refactor this in order to still use the factory pattern, but without the virtual member call in the constructor.

根据定义

In class-based programming, the factory method pattern is a creational pattern which uses factory methods to deal with the problem of creating objects without specifying the exact class of object that will be created.

工厂方法不打算从构造函数中使用,因为将创建的对象的确切类在构造时是已知的。例如,

  • 如果Document必须在施工时创建所有Pages,那么它可以在不使用工厂方法的情况下创建,因为它完全知道要创建的所有页面。

  • 但如果Document必须在施工后或多次创建Pages,那么工厂方法是有用的。

  • I much more prefer the situation where creating a new Resume is all that's needed to actually create a Resume containing pages.

    因此,如果所有Pages都是在施工时创建的,那么可以在不使用工厂方法模式的情况下重写此具体示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class Document
        private readonly PageList as IList(of IPage)

        public readonly property Pages as IEnumerable(of IPage)
            get
                return PageList
            end get
        end property

        public sub new()
            Me.PageList = new List(of IPage)
        end sub

        protected sub Add(paramarray Pages() as IPage)
            Me.Pages.AddRange(Pages)
        end sub
    end public

    public class Resume
        inherits Document

        public sub new()
            mybase.add(new SkillsPage, new EducationPage, new ExperiencePage)
        end sub
    end class
  • 方法Add允许从Resume构造函数中使用,因为Document对象已完全构造并准备好使用。

  • 方法Add受保护,因此只能从Document或派生类添加页。


  • 这个怎么样?它使用惰性初始化,只在需要时创建页面(而不是在构造函数中创建页面)。

    另外,请注意,工厂方法可见性更改为protected,以使其不被公众使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    abstract class Document{
        protected List<Page> _pages = new List<Page>();

        // Constructor calls abstract Factory method
        public Document(){}

        public List<Page> Pages
        {
            get { CreatePages(); return _pages; }
        }

        // Factory Method
        protected abstract void CreatePages();
    }

    class Resume : Document{
        // Factory Method implementation
        protected override void CreatePages(){
           if(pages.Count == 0 ){
            _pages .Add(new SkillsPage());
            _pages .Add(new EducationPage());
            _pages .Add(new ExperiencePage());
           }
        }
    }

    编辑建议:我个人不喜欢使用全局_pages变量,因为如果在许多方法和线程之间共享,它可能会给您带来麻烦。我宁愿选择GoF手册中描述的工厂方法模式。我的建议是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    abstract class Document{
        public IEnumerable<Page> Pages{
            get { return CreatePages();}
        }

        // Factory Method
        protected abstract IEnumerable<Page> CreatePages();
    }

    class Resume : Document{
        // Factory Method implementation
        protected override IEnumerable<Page> CreatePages(){
             List<Page> _pages = new List<Page>();
            _pages .Add(new SkillsPage());
            _pages .Add(new EducationPage());
            _pages .Add(new ExperiencePage());
            return _pages;
           }
        }
    }


    您可以在属性本身中进行。

    在这种情况下,可以在基类中将Pages属性标记为virtual

    Resume的假设代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        private List<Page> _pages  = null;
        public override List<Page> Pages
        {
              get
              {  
                    if(pages == null)
                    {
                      _pages = new List<Page>();
                      _pages .Add(new SkillsPage());
                      _pages .Add(new EducationPage());
                      _pages .Add(new ExperiencePage());
                    }

                    return _pages;
               }

            }
        }

    在这种情况下,页面将在首次访问Pages属性时创建,而不是在ctor执行时创建。