Как сделать фабрику классов для создания требуемого производного класса

Я часто использую шаблон фабрики классов, согласно которому у класса есть частный конструктор и статический метод для создания класса. Это допускает ситуацию, когда класс не может быть построен по какой-либо причине, и возвращается значение null - очень удобно.

Я хотел бы иметь возможность расширить это до фабричного метода, который создает определенный класс из иерархии производных классов в зависимости от условий. Однако я не вижу способа скрыть конструкторы производных классов, чтобы принудительно использовать фабричный метод. Если фабричный метод находится в базовом классе, он больше не имеет доступа к частным конструкторам производных классов. Помещение фабричного метода в каждый производный класс не работает, поскольку требуемый тип должен быть известен заранее. Вложенные классы могли бы быть способом, если бы у класса был доступ к закрытым членам вложенного класса, но, к сожалению, кажется, что вложенные классы имеют доступ к закрытым членам включающего класса, но не наоборот.

Кто-нибудь знает, как это сделать?


person Dave    schedule 20.02.2013    source источник


Ответы (4)


Вот пример кода, над которым я работал, когда Дэниел опубликовал свой ответ. Похоже, он делает то, что он предложил:

public static class BaseFactory
{
    public static Base Create(bool condition)
    {
        if (condition)
        {
            return Derived1.Create(1, "TEST");
        }
        else
        {
            return Derived2.Create(1, DateTime.Now);
        }
    }
}

public class Base
{
    protected Base(int value)
    {
    }

    protected static Base Create(int value)
    {
        return new Base(value);
    }
}

public sealed class Derived1: Base
{
    private Derived1(int value, string text): base(value)
    {
    }

    internal static Derived1 Create(int value, string text)
    {
        return new Derived1(value, text);
    }
}

public sealed class Derived2: Base
{
    private Derived2(int value, DateTime time): base(value)
    {
    }

    internal static Derived2 Create(int value, DateTime time)
    {
        return new Derived2(value, time);
    }
}

[РЕДАКТИРОВАТЬ] И второе предложение Дэниела:

public static class BaseFactory
{
    public static Base Create(bool condition)
    {
        if (condition)
        {
            return new Derived1Creator(1, "TEST");
        }
        else
        {
            return new Derived2Creator(1, DateTime.Now);
        }
    }

    private sealed class Derived1Creator: Derived1
    {
        public Derived1Creator(int value, string text): base(value, text)
        {
        }
    }

    private sealed class Derived2Creator: Derived2
    {
        public Derived2Creator(int value, DateTime time): base(value, time)
        {
        }
    }
}

public class Base
{
    protected Base(int value)
    {
    }

    protected static Base Create(int value)
    {
        return new Base(value);
    }
}

public class Derived1: Base
{
    protected Derived1(int value, string text): base(value)
    {
    }

    protected static Derived1 Create(int value, string text)
    {
        return new Derived1(value, text);
    }
}

public class Derived2: Base
{
    protected Derived2(int value, DateTime time): base(value)
    {
    }

    protected static Derived2 Create(int value, DateTime time)
    {
        return new Derived2(value, time);
    }
}

Обратите внимание, что этот второй подход означает, что, к сожалению, классы не могут быть запечатаны.

person Matthew Watson    schedule 20.02.2013
comment
Дэниел и Мэтью. Действительно умно. Он работает и выполняет все требования. Мне это нравится. Я просто не знаю, чей ответ отметить как принятый :-) - person Dave; 20.02.2013
comment
Мэтью, первый код - это не то, что я имел в виду в своем первом варианте. Как я уже сказал, создайте конструктор internal. Ваш обход с дополнительными фабричными методами не принесет никакой пользы. - person Daniel Hilgarth; 20.02.2013
comment
Достаточно честно. Он был просто предназначен для демонстрации использования фабричного метода - хотя фабричный метод в данном случае настолько прост, что ничего не скрывает. В общем случае вполне могло быть. - person Matthew Watson; 20.02.2013
comment
@Dave: Ну, Дэниел был первым, кто опубликовал код, поэтому, если другие факторы совпадают, вы должны отметить его. :) - person Matthew Watson; 20.02.2013

Есть несколько возможностей, две из которых:

  1. Объедините все эти классы в один проект и создайте конструкторы internal. Другие проекты не смогут вызывать эти конструкторы, но код внутри этого проекта может.
  2. Сделайте конструкторы этих классов protected (вместо private) и создайте частный производный класс в классе, содержащем фабричный метод. Создайте экземпляр этого частного класса и верните его.

Пример для второго варианта:

public static class AnimalFactory
{
    public static Animal Create(int parameter)
    {
        switch(parameter)
        {
            case 0:
                return new DogProxy();
            case 1:
                return new CatProxy();
            default:
                throw new ArgumentOutOfRangeException("parameter");
        }
    }

    private class DogProxy : Dog { }

    private class CatProxy : Cat { }
}

public abstract class Animal { }

public class Dog : Animal
{
    protected Dog() { }
}

public class Cat : Animal
{
    protected Cat() { }
}
person Daniel Hilgarth    schedule 20.02.2013
comment
Дэниел и Мэтью. Действительно умно. Он работает и выполняет все требования. Мне это нравится. Я просто не знаю, чей ответ отметить как принятый :-) - person Dave; 20.02.2013
comment
@ Дэйв: Ты должен решить это сам. Обычно вы соглашаетесь с ответом, который помог вам больше всего. Если есть несколько ответов, которые помогли вам сравняться, вы либо не принимаете ни одного, либо тот, который вам больше нравится, либо был опубликован первым. - person Daniel Hilgarth; 20.02.2013

Вместо того, чтобы использовать методы внутри самого класса в качестве фабрики, реализуйте шаблон Factory с помощью статического класса («фабрика»), который возвращает правильный экземпляр на основе написанной вами логики.

person dutzu    schedule 20.02.2013
comment
Это не решает проблему: запретить пользователям создавать экземпляры с помощью new. Но я согласен с тем, что строительство следует отдать третьему классу. Базовый класс не должен знать о классах, производных от него. - person Daniel Hilgarth; 20.02.2013
comment
@DanielHilgarth Вы правы, для этого он должен разделить типы в другом проекте как внутренние и выставлять их через интерфейсы. - person dutzu; 20.02.2013
comment
Пожалуйста, перечитайте вопрос. Цитирую: Однако я не вижу способа затем скрыть конструкторы производных классов, чтобы принудительно использовать фабричный метод. - person Daniel Hilgarth; 20.02.2013

Вы можете перехватить создание производного типа в конструкторе базового класса и проверить, что вызывающий объект является вашей фабрикой, используя StackFrames:

 protected Class1() //base class ctor
    {
        StackFrame[] stackFrames = new StackTrace().GetFrames(); 
        foreach (var frame in stackFrames)
        {
            //check caller and throw an exception if not satisfied
        }
    }
person Stefano Altieri    schedule 20.02.2013