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

时间:2012-06-29 21:14:42

标签: c# design-patterns factory-method

www.dofactory.com上我找到了工厂模式的真实世界示例。但是代码在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());
    }
}

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

Document document = new Resume();

我明白为什么在构造函数中调用虚拟成员是个坏主意(如here所述)。

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

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

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

我更喜欢这样的情况,即创建新的Resume就是实际创建包含页面的Resume所需的全部内容。

4 个答案:

答案 0 :(得分:2)

重构此方法的一种方法是提前传递页面,并将它们传递给受保护的构造函数:

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

P.S。我不确定这与工厂方法有什么关系。您的帖子说明了Template Method Pattern

答案 1 :(得分:1)

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

另请注意,工厂方法可见性已更改为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());
       }
    }
}

编辑建议: 我个人不喜欢拥有全局_pages变量,因为如果在许多方法和线程之间共享,它可能会让您遇到麻烦。我宁愿选择GoF书中描述的工厂方法模式。这是我的建议:

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

答案 2 :(得分:1)

  

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

根据定义

  

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

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

  1. 如果Document必须在施工时创建所有Pages,那么它可以在没有工厂方法的情况下完成,因为它确切知道要创建的所有页面

  2. 但是如果Document必须在构建后稍后创建Pages并且可能多次,那么工厂方法很有用

  3.   

    我更喜欢这样的情况,即创建新Resume就是实际创建包含页面的Resume所需的全部内容。

    因此,如果要在构造时创建所有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
    
    1. 允许在Add构造函数中使用方法Resume,因为Document对象已完全构建并可以使用。

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

答案 3 :(得分:0)

你可以在财产本身做。

在这种情况下,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; 
           }

        }
    }

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