C# 如何防止工厂方法模式导致构造函数中虚拟成员调用的警告?

C# 如何防止工厂方法模式导致构造函数中虚拟成员调用的警告?,c#,design-patterns,factory-method,C#,Design Patterns,Factory Method,在上,我发现了一个真实的工厂模式示例。但是代码在ReSharper中生成了一个关于构造函数中的虚拟成员调用的警告 引起警告的代码如下所示: abstract class Document { private List<Page> _pages = new List<Page>(); // Constructor calls abstract Factory method public Document() { this.C

在上,我发现了一个真实的工厂模式示例。但是代码在ReSharper中生成了一个关于构造函数中的虚拟成员调用的警告

引起警告的代码如下所示:

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());
    }
}
我确实理解为什么在构造函数中调用虚拟成员是个坏主意(如前所述)

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

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

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

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

您可以在属性本身中完成

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

Resume
的假设代码可能如下所示:

    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; 
           }

        }
    }
private List\u pages=null;
公共覆盖列表页
{
得到
{  
如果(页面==null)
{
_pages=新列表();
_pages.Add(new SkillsPage());
_添加(新教育页面());
_添加(新体验页面());
}
返回页面;
}
}
}

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

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

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()
        };
    }
}
公共抽象类文档{
受保护文档(IEnumerable页){
//如果可以添加到_页面,请不要使用AsReadOnly
_pages=pages.ToList().AsReadOnly();
}
// ...
}
公开课简历:文件{
公共简历():基本(CreatePages()){
}
私有静态IEnumerable CreatePages(){
返回新页面[]{
新技能集(),
新建教育页面(),
新体验()
};
}
}

另外,我不确定这与工厂方法有什么关系。你的帖子说明了这个问题。

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

另外,请注意,factory方法可见性已更改为
protected
,以将其隐藏起来,不供公众使用

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());
       }
    }
}
我的问题是如何重构它,以便仍然使用工厂模式,但不在构造函数中调用虚拟成员

根据定义

在基于类的编程中,工厂方法模式是一种创造性模式,它使用工厂方法来处理创建对象的问题,而不指定要创建的对象的确切类

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

  • 如果
    文档
    必须在构建时创建所有
    页面
    ,则无需工厂方法即可创建,因为它完全知道要创建的所有页面

  • 但如果
    文档
    必须在构建之后创建
    页面
    ,可能需要多次,那么工厂方法是有用的

  • 我更喜欢这样的情况,即创建一份新的简历才是真正创建一份包含页面的简历所需要的

    因此,如果在构建时创建所有
    页面
    ,则无需使用工厂方法模式即可重写此具体示例:

    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
    或派生类添加页面


  • 只需将
    Resume
    标记为
    sealed
    ?@EkoostikMartin如果OP明天醒来想要声明
    styleshresume:Resume
    ?@EkoostikMartin:我不只是想警告消失,我想知道如何实现这一点。仍然可以对
    Resume
    进行子类化。我建议寻找更好的真实示例。这将在
    页面
    的每次访问中不断添加页面。我想在
    Pages
    属性中,你会检查
    \u Pages==null
    并且只调用
    CreatePages
    。你的代码没有意义,
    CreatePages()
    有一个无效的返回,那么
    myResume.Pages
    在这个设计中给了我什么?@EkoostikMartin是的,我刚刚看到了!现在修好了。感谢tipI,我希望检查
    \u pages==null
    \u pages.Count==0
    在抽象类中。@comecme Yes这是一个选项。但是,这种方式更加灵活,因为子类可以选择要做什么,而不是对所有子类都有一个通用行为。我认为这不符合工厂方法的条件,因为已经没有方法了。好吧,您可以像“方法”一样使用
    属性。为什么你想要和方法成员紧密结合……根据这是一个工厂方法的例子。他们错了吗?@comecme是的,我有理由肯定他们错了:首先,工厂方法不能是
    void
    ,它必须返回一些东西。
    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;
           }
        }
    }
    
    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