Как сделать фабрику классов для создания необходимого производного класса
Я часто использую шаблон фабрики классов, в соответствии с которым класс имеет собственный конструктор и статический метод для создания класса. Это учитывает ситуацию, когда класс не может быть построен по какой-то причине, и возвращается нуль - очень удобно.
Я хотел бы иметь возможность распространить это на фабричный метод, который создает определенный класс из иерархии производных классов в зависимости от условий. Однако я не вижу способа скрыть конструкторы производных классов для принудительного использования фабричного метода. Если метод фабрики находится в базовом классе, он больше не имеет доступа к закрытым конструкторам производных классов. Помещение фабричного метода в каждый производный класс не работает, так как требуемый тип должен быть известен заранее. Вложенные классы могут быть способом, если у класса есть доступ к закрытым членам вложенного класса, но, к сожалению, кажется, что вложенные классы имеют доступ к закрытым членам включающего класса, но не наоборот.
Кто-нибудь знает способ сделать это?
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);
}
}
Обратите внимание, что этот второй подход означает, что классы не могут быть запечатаны, к сожалению.
Есть несколько возможностей, две из которых:
- Поместите все эти классы в один проект и сделайте конструкторы
internal
, Другие проекты не смогут вызывать эти конструкторы, но код внутри этого проекта может. - Сделайте конструкторы этих классов
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 посредством статического класса ("фабрика"), который возвращает правильный экземпляр на основе написанной вами логики.