すべてのクラスで使用される基本プロパティとメソッドを使用してインターフェイスを作成しました。
クラスは実装クラスを意味すると思います。
一部のクラスには、基本実装を拡張するための他のプロパティがあります。
これらの他のプロパティがそれらのクラスのクライアントの観点から本当に必要かどうか、またはそれらが実際には単なるプライベート実装の詳細であるかどうかを理解することは、さらなる議論のために不可欠です。
それらがプライベートな実装の詳細であると仮定すると、それらを公開してはならず、インターフェイスインスタンスとしてオブジェクトにアクセスし続けることができます。
クライアントが他のプロパティを使用する必要がある場合、インターフェイスインスタンスでのリフレクションを介してそれらにアクセスしていると、LSP(LIskov Substitution Principle、SOLID原則の1つ)に違反していることになります。これらの原則は規則ではありませんが、それらに従うことはあなたに大いに役立つことに注意してください。LSPに違反すると、その単一のインターフェイスの背後で、これまでのようにオブジェクトを抽象化する必要がないことがわかります。この場合、実際のタイプを確認するためにキャスティングまたはasまたはis演算子を使用することは完全に合法ですが、口の中に良い味を残したり、後で良い胃の感覚を残したりすることはありません。
リフレクションまたはメソッド以外に、クライアント側の拡張プロパティを公開/操作するための追加のプロパティを取得および設定する方法はありますか?
これは1つの方法です。
まず、今日、これは、追加のプロパティがない「単純な」場合の状態です。
interface ICar
{
void Accelerate();
}
class CompactCar : ICar
{
public void Accelerate()
{
// Slooooow acceleration
}
}
class NasCar : ICar
{
public void Accelerate()
{
// Yiehaa!
}
}
sealed class TrafficSimulator
{
private readonly List<ICar> _cars = new List<ICar>();
public void AddCar(ICar car)
{
_cars.Add(car);
car.Accelerate();
}
}
次に、特定のコンクリートタイプにいくつかのプロパティを追加し、他のタイプにいくつかのプロパティを追加します。
class CompactCar : ICar
{
// Can only be used *before* the car is accelerated
public bool CanOnlyTurn { get; set; }
...
}
class NasCar : ICar
{
// Can only be used *after* the car is accelerated
public string NumberPlate { get; set; }
...
}
sealed class TrafficSimulator
{
...
public void AddCar(ICar car)
{
_cars.Add(car);
// Check if there's something to do before accelerating
CompactCar compact = car as CompactCar;
if (compact != null)
{
compact.CanOnlyTurn = true;
}
car.Accelerate();
// Check if there's something to do after accelerating
NasCar nascar = car as NasCar;
if (nascar != null)
{
nascar.NumberPlate = "I rule!";
}
}
}
さて、あなたがそれを続けるとき、上記は本当の混乱になるでしょう。それがLSPに違反することであなたにもたらされるものです。これらの追加のプロパティを追加する理由は常にあります。上記の場合、加速前に1つの「特別な」ことを行い、加速後に別の「特別な」ことを行うことがあります。
この特定のケースは、これによってある程度解決できます。
interface ICarSetup
{
void BeforeAccelerate();
}
interface ICarTeardown
{
void AfterAccelerate();
}
class ActionSetup : ICarSetup
{
private readonly Action _action;
public ActionSetup(Action action)
{
_action = action;
}
public void BeforeAccelerate()
{
_action();
}
}
class NullSetup : ICarSetup
{
public void BeforeAccelerate()
{
}
}
class ActionTeardown : ICarTeardown
{
private readonly Action _action;
public ActionTeardown(Action action)
{
_action = action;
}
public void AfterAccelerate()
{
_action();
}
}
class NullTeardown : ICarTeardown
{
public void AfterAccelerate()
{
}
}
sealed class TrafficSimulatorDriver
{
private TrafficSimulator _trafficSimulator;
public void RunTheSimulation()
{
var nascar = new NasCar();
var nascarSetup = new NullSetup();
var nascarTeardown = new ActionTeardown(() => nascar.NumberPlate = "I rule!");
_trafficSimulator.AddCar(nascar, nascarSetup, nascarTeardown);
var compact = new CompactCar();
var compactSetup = new ActionSetup(() => compact.CanOnlyTurn = true);
var compactTeardown = new NullTeardown();
_trafficSimulator.AddCar(compact, compactSetup, compactTeardown);
}
}
sealed class TrafficSimulator
{
...
public void AddCar(ICar car, ICarSetup setup, ICarTeardown teardown)
{
_cars.Add(car);
setup.BeforeAccelerate();
car.Accelerate();
teardown.AfterAccelerate();
}
}
これが適切な使用法でない場合は、DIを使用せず、クライアントでこれらのクラスのハードインスタンス化を維持する方がよいでしょうか。
これら2つの問題を混同しないでください。DIとハードインスタンス化は相互に排他的ではありません。インターフェイスインスタンスの形式で依存関係を注入しないからといって、具体的なインスタンスを注入できないわけではありません。