レガシーコードベースをリファクタリングしています。
ポリモーフィックになるための良いターゲットになると判断した非常に類似した4つのオブジェクトがあるので、すべての一般的なコードを基本クラスに移動し、インターフェイスを追加しました。
これらのオブジェクトを作成しようとすると、実際の問題が発生します。すべてのクラスには、それぞれ5〜10個のパラメーターを持つ3つの異なるコンストラクターがあり、コンテキストに基づいてさまざまなケースで使用されます。
これらのクラスの処理が多すぎることはすでにわかっており、実際には、それぞれが1つのケースを処理するさまざまな小さなクラスに分割する方が適切ですが、これはレガシーコードベースであり、あまり多くの変更を行うことはできません既存のコードに。
現時点では、私のコードは次のようなものです。
public class HandlerFactory
{
public static IHandler CreateInstance(HandlerType param1, string param2, ..., string param8)
{
switch(param1)
{
case HandlerType.Type1
return new Handler1(param2, ..., param8)
case HandlerType.Type2
return new Handler2(param2, ..., param8)
case HandlerType.Type3
return new Handler3(param2, ..., param8)
case HandlerType.Type4
return new Handler4(param2, ..., param8)
}
}
public static IHandler CreateInstance(HandlerType param1, int param2, ..., DateTime param10)
{
switch(param1)
{
case HandlerType.Type1
return new Handler1(param2, ..., param10)
case HandlerType.Type2
return new Handler2(param2, ..., param10)
case HandlerType.Type3
return new Handler3(param2, ..., param10)
case HandlerType.Type4
return new Handler4(param2, ..., param10)
}
}
public static IHandler CreateInstance(HandlerType param1, string param2, ..., CustomClass param5)
{
switch(param1)
{
case HandlerType.Type1
return new Handler1(param2, ..., param5)
case HandlerType.Type2
return new Handler2(param2, ..., param5)
case HandlerType.Type3
return new Handler3(param2, ..., param5)
case HandlerType.Type4
return new Handler4(param2, ..., param5)
}
}
}
ただし、別のハンドラーを追加する場合、3つのファクトリーメソッドを編集する必要があるため、私は3つのファクトリーメソッドを使用するのが嫌いです(このリファクタリングを行う主な理由)。
1つのスイッチで1つのファクトリメソッドのみを保持できる方法はありますか?
@JohnWuが提供する答えは非常にエレガントで、とても気に入っています。代替案を提供させてください。
Abstract Factory Pattern を使用できます。基本的に1つの大きな工場があり、コンクリートハンドラー用のコンクリート工場を生産します。次に、別のハンドラーが必要な場合は、その特定のハンドラー用に1つのファクトリーを作成するだけです。次のようなものになります。
public interface IHandler
{
}
public class Handler1 : IHandler
{
public Handler1(string param1, string param2)
{
}
public Handler1(int param1, int param2)
{
}
}
public class Handler2 : IHandler
{
public Handler2(string param1, string param2)
{
}
public Handler2(int param1, int param2)
{
}
}
簡単にするために、2つの異なるコンストラクターを持つ2つのHandlerクラスしかありません。ソリューションはまだあなたのケースに適用できると思います。次に、工場があります。
public interface IHandlerFactory
{
IHandler Create(string param1, string param2);
IHandler Create(int param1, int param2);
}
public class Handler1Factory : IHandlerFactory
{
public IHandler Create(int param1, int param2)
{
return new Handler1(param1, param2);
}
public IHandler Create(string param1, string param2)
{
return new Handler1(param1, param2);
}
}
public class Handler2Factory : IHandlerFactory
{
public IHandler Create(int param1, int param2)
{
return new Handler2(param1, param2);
}
public IHandler Create(string param1, string param2)
{
return new Handler2(param1, param2);
}
}
public class HandlerFactory
{
public static IHandler Create(IHandlerFactory factory, string param1, string param2)
{
return factory.Create(param1, param2);
}
public static IHandler Create(IHandlerFactory factory, int param1, int param2)
{
return factory.Create(param1, param2);
}
}
ここでの代替策は、HandlerFactory
タイプのプロパティを持つIHandlerFactory
クラスを使用することであり、メソッドは静的ではなく、IHandlerFactory
パラメータを持たないことになります。明らかに、HandlerFactory
クラスのオブジェクトは、ある時点でインスタンス化する必要があります。
使い方は次のようになります:
public class Example
{
public void ExampleMethod()
{
Handler1Factory factory1 = new Handler1Factory();
var handler1 = HandlerFactory.Create(factory1, "parameter1", "parameter2");
Handler2Factory factory2 = new Handler2Factory();
var handler2 = HandlerFactory.Create(factory2, 1, 2);
}
}
このソリューションでは、コードを目的別に明確に分けています。最も重要なのは、別のハンドラーが必要になった場合に、既存のコードを変更する必要がないことです。必要なことは、その特定のハンドラーのファクトリーを作成することだけです。
すべてのハンドラーが同じパラメーターリストを持つコンストラクターを持っているため、これが可能であることに注意してください。そうでなければ、デザインは多少異なります。
このソリューションは、ケース/スイッチを排除し、一般的な引数に置き換えます。通常、コンストラクター引数を許可しないnew
制約を使用しない限り、この方法でオブジェクトを作成することはできません。私は この回避策 を別の答えから盗みました。
また、ここにはファクトリメソッドが1つだけあり、コンストラクタ引数は params キーワードを使用して可変サイズのリストとして受け入れられます。
public class HandlerFactory
{
public static T CreateInstance<T>(params object[] constructorArguments) where T : class, IHandler
{
return (T)Activator.CreateInstance(typeof(T), constructorArguments);
}
}
ご覧のとおり、このメソッドははるかに短く、ケース/スイッチがなく、プロトタイプが1つだけ必要です。そして、素敵なのはHandler4
またはその他のサブクラスの場合、ファクトリを操作する必要はありません。ただHandler4
はIHandler
を実装しており、これで準備は完了です。
次のように呼び出すことができます。
var o = HandlerFactory.CreateInstance<Handler3>(param1, param2);
戻り値の型はT
なので、o
をvar
として宣言するか、コンパイラーが具象型を推測するかを選択できます。
IHandler o = HandlerFactory.CreateInstance<Handler3>(param1, param2);
インターフェースのみで作業したい場合。
おっしゃったように、既存のコードにはそのまま設計上の問題があります。まず、非常に多くの(異なる!)パラメーターを持つメソッドとコンストラクターがあると、デザインのにおいがします。
複雑さの根本的な原因を修正しない場合は、抽象化の別のレイヤーを追加することで、人生を楽にすることができます。これは、コードをリファクタリングする(または新しいコードに移動する)までの時間を確保するまで、コードベースの維持を容易にします。
DonNet Fiddle使用例: https://dotnetfiddle.net/NoChOk
閲覧のためのコード:
using System;
public class Program
{
public void Main()
{
IHandler first = HandlerFactory.CreateInstance(
HandlerType.Handler1,
ParamBuilder.start().withOne("x").withTwo("y").build());
IHandler second = HandlerFactory.CreateInstance(
HandlerType.Handler2,
ParamBuilder.start().withAlpha(1).withBeta(new DateTime()).build());
IHandler third = HandlerFactory.CreateInstance(
HandlerType.Handler3,
ParamBuilder.start().withMickey(":)").withMouse(1L).build());
first.DoStuff();
second.DoStuff();
third.DoStuff();
}
class HandlerFactory
{
public static IHandler CreateInstance(HandlerType type, IParam param)
{
switch (type) {
case HandlerType.Handler1: return new Handler1(param);
case HandlerType.Handler2: return new Handler2(param);
case HandlerType.Handler3: return new Handler3(param);
default: throw new NotSupportedException();
}
}
}
enum HandlerType { Handler1, Handler2, Handler3
}
interface IHandler { void DoStuff(); }
class Handler1 : IHandler {
private Param1 numbers; private Param2 letters; private Param3 disney;
public Handler1(IParam param) {
if (param is Param1) numbers = (Param1) param;
if (param is Param2) letters = (Param2) param;
if (param is Param3) disney = (Param3) param;
}
public void DoStuff() { Console.WriteLine("I am alive - Handler 1");}
}
class Handler2 : IHandler {
private Param1 numbers; private Param2 letters; private Param3 disney;
public Handler2(IParam param) {
if (param is Param1) numbers = (Param1) param;
if (param is Param2) letters = (Param2) param;
if (param is Param3) disney = (Param3) param;
}
public void DoStuff() { Console.WriteLine("I am alive - Handler 2");}
}
class Handler3 : IHandler {
private Param1 numbers;
private Param2 letters;
private Param3 disney;
public Handler3(IParam param) {
if (param is Param1) numbers = (Param1) param;
if (param is Param2) letters = (Param2) param;
if (param is Param3) disney = (Param3) param;
}
public void DoStuff() { Console.WriteLine("I am alive - Handler 3");}
}
interface IParam {
string One { get; } string Two { get; } // first param class
int Alpha { get; } DateTime Beta { get; } // second param class
string Mickey { get; } long Mouse { get; } // third param class
}
class ParamBuilder {
private string one, two;
private int? alpha; private DateTime? beta;
private string mickey; private long? mouse;
public static ParamBuilder start() { return new ParamBuilder(); }
public IParam build() {
if (!string.IsNullOrEmpty(one) && !string.IsNullOrEmpty(one)) {
return new Param1(one, two);
}
if (alpha.HasValue && beta.HasValue) {
return new Param2(alpha.Value, beta.Value);
}
if (!string.IsNullOrEmpty(mickey) && mouse.HasValue) {
return new Param3(mickey, mouse.Value);
}
throw new NotSupportedException();
}
public ParamBuilder withOne(string one) { this.one = one; return this; }
public ParamBuilder withTwo(string two) { this.two = two; return this; }
public ParamBuilder withAlpha(int alpha) { this.alpha = alpha; return this; }
public ParamBuilder withBeta(DateTime beta) { this.beta = beta; return this; }
public ParamBuilder withMickey(string m) { this.mickey = m; return this; }
public ParamBuilder withMouse(long m) { this.mouse = m; return this; }
}
class Param1 : IParam {
public string One { private set; get; }
public string Two { private set; get; }
public int Alpha { get {throw new NotImplementedException();} }
public DateTime Beta { get {throw new NotImplementedException();} }
public string Mickey { get {throw new NotImplementedException();} }
public long Mouse { get {throw new NotImplementedException();} }
public Param1(string one, string two) {
One = one; Two = two;
}
}
class Param2 : IParam {
public int Alpha { private set; get; }
public DateTime Beta { private set; get; }
public string One { get {throw new NotImplementedException();} }
public string Two { get {throw new NotImplementedException();} }
public string Mickey { get {throw new NotImplementedException();} }
public long Mouse { get {throw new NotImplementedException();} }
public Param2(int alpha, DateTime beta) {
Alpha = alpha; Beta = beta;
}
}
class Param3 : IParam {
public string Mickey { private set; get; }
public long Mouse { private set; get; }
public string One { get {throw new NotImplementedException();} }
public string Two { get {throw new NotImplementedException();} }
public int Alpha { get {throw new NotImplementedException();} }
public DateTime Beta { get {throw new NotImplementedException();} }
public Param3(string mi, long mo) {
Mickey = mi; Mouse = mo;
}
}
}