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

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

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

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

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

[EDIT] И для второго предложения Даниила:

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

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

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

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

Вы можете перехватить создание производного типа в конструкторе базового класса и проверить, является ли вызывающая сторона вашей фабрикой с помощью 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
        }
    }

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

Другие вопросы по тегам