0

Open-Closed Principle に基づいていくつかのコードをリファクタリングしようとしましたが、デザイン パターンの適用に関しては、次のクラスを正しく取得できないようです。(以下に概説する多くのクラスをお詫びします - 可能な限りそれらを減らしましたが、残りは私のデザインを示すために必要です)。

セットアップは、次のクラスで構成されます。

public interface IPortFactory
{
    IPort CreatePort(int id, PortDetails details);
}

public class PtpPortFactory : IPortFactory
{
    public IPort CreatePort(int id, PortDetails details)
    {
        var ptpPortDetails = details as PtpPortDetails;
        if (ptpPortDetails == null)
        {
            throw new ArgumentException("Port details does not match ptp ports", "details");
        }

        return new PtpPort(id, FiberCapability.FromValue(ptpPortDetails.Capability));
    }
}

public interface IPort
{
    int Id { get; }
}

public interface IInternetPort : IPort
{
    bool OfCapability(FiberCapability capability);
}

public class PtpPort : IInternetPort
{
    private readonly FiberCapability _capability;

    public PtpPort(int id, FiberCapability capability)
    {
        _capability = capability;
        Id = id;
    }

    public int Id { get; private set; }

    public bool OfCapability(FiberCapability capability)
    {
        return capability.Equals(_capability);
    }
}

その上PtpPort、私はPonPort実装していますが、実装IInternetPortしているCatvPortだけIPortです。

すでにこのコードには、コード臭の兆候があると思います。inCreatePortでは、キャストするのではなく、PtpPortFactory受け入れるPtpPortDetails(から継承する) ことができます。PortDetailsただし、そうすると、PonPortFactoryも実装する を作成できません。IPortFactoryこれらのポートにはPonPortDetails. またはCatvPortFactoryそのことについて。

ポート ファクトリを使用すると、別のコードの匂いがします。

PortType portType = command.PortType;
IPortFactory portFactory = portType.GetPortFactory();
var portsToSelectFrom = ports.Select(port => (IInternetPort) portFactory.CreatePort(port.Id, port.PortDetails)).ToList();

IPortfrom からto へのダウンキャストを実行する必要はIInternetPortなく、単にCreatePortreturnが必要IInternetPortです。

上記を理解するために必要な最後の情報は、おそらく次のクラスです ( Jimmy Bogards クラスに基づくEnumeration)。

public abstract class PortType : Enumeration<PortType, int>
{
    public static readonly PortType Ptp = new PtpPortType();
    public static readonly PortType Pon = new PonPortType();
    public static readonly PortType Catv = new CatvPortType();

    protected PortType(int value, string description)
        : base(value, description) { }

    public abstract IPortFactory GetPortFactory();

    private class CatvPortType : PortType
    {
        public CatvPortType() : base(2, "catv") { }

        public override IPortFactory GetPortFactory()
        {
            return new CatvPortFactory();
        }
    }

    private class PonPortType : PortType
    {
        public PonPortType() : base(1, "pon") { }

        public override IPortFactory GetPortFactory()
        {
            throw new NotImplementedException("Pon ports are not supported");
        }
    }

    private class PtpPortType : PortType
    {
        public PtpPortType() : base(0, "ptp") { }

        public override IPortFactory GetPortFactory()
        {
            return new PtpPortFactory();
        }
    }
}

途中で誰かが私を助けてくれることを本当に願っています(ジェネリックを導入しようとしましたが、戻り値の型の共分散をサポートしていないC#の障壁に常にぶつかっているようです)。

さらに、より良いコードを書くための道に沿って私を助けるための他のヒントとコツは大歓迎です。

アップデート

コメントでリクエストがあったため、以下にさらにコードを追加しました。

public Port Handle(TakeInternetPortCommand command)
{
    var portLocatorService = new PortLocatorService();
    IList<Port> availablePorts = portLocatorService.FindAvailablePorts(command.Pop, command.PortType);

    PortType portType = command.PortType;
    IPortFactory portFactory = portType.GetPortFactory();
    var portsToSelectFrom = ports.Select(port => (IInternetPort) portFactory.CreatePort(port.Id, port.PortDetails)).ToList();

    IPort port = _algorithm.RunOn(portsToSelectFrom);
    Port chosenPort = availablePorts.First(p => p.Id == port.Id);
    chosenPort.Take(command.Spir);
    _portRepository.Add(chosenPort);
    return chosenPort;
}

Port突然タイプもあるという事実に混乱しないでください。これは別の境界付けられたコンテキスト (DDD の意味で) の集約です。IInternetPortアルゴリズムは、メソッドをOfCapability内部的に使用してポートを選択するため、 のリストを入力として受け取る必要があります。ただし、アルゴリズムが正しいポートを選択した場合、単に に関心があるだけなIdので、戻り値の型は単にIPortです。

4

2 に答える 2

2

あなたが解決しようとしている問題 (設計上の問題ではなく、ビジネス上の問題) をどのように理解するかを以下に示します。

ポートのリストがあり、(アルゴリズムに固有のいくつかの基準に基づいて) リストから単一のポートを選択するリストに対してアルゴリズムを実行する必要があります。

ここでは、これをモデル化する方法を提案します。次の入力クラスがあると仮定します。

public class PortInput
{
    public int Id { get; set; }

    public PortDetails PortDetails { get; set; }
}

これは、質問の Port クラスの一部に対応しています。

そして、ここにアルゴリズムのインターフェースがあります:

public interface IPortSelectionAlgorithm
{
    int SelectPort(PortInput[] port_inputs);
}

このインターフェースは、解決したい問題をモデル化していることに注意してください。つまり、ポートのリストが与えられた場合 -> 1 つを選択する必要があります

そして、これはそのようなアルゴリズムインターフェースの実装です:

public class PortSelectionAlgorithm : IPortSelectionAlgorithm
{
    private readonly ICapabilityService<PortDetails> m_CapabilityService;

    public PortSelectionAlgorithm(ICapabilityService<PortDetails> capability_service)
    {
        m_CapabilityService = capability_service;
    }

    public int SelectPort(PortInput[] port_inputs)
    {
        //Here you can use m_CapabilityService to know if a specific port has specific capability

        ...
    }
}

この実装が宣言しているのは、ポートの詳細に基づいてポートの機能を取得する方法を知っているサービスが必要であることです。このようなサービス契約の定義は次のとおりです。

public interface ICapabilityService<TDetails> where TDetails : PortDetails
{
    bool OfCapability(TDetails port_details, FiberCapability capability);
}

ポートの種類ごとに、次のようなサービスの実装を作成できます。

public class PtpPortCapabilityService: ICapabilityService<PtpPortDetails>
{
    public bool OfCapability(PtpPortDetails port_details, FiberCapability capability)
    {
        ...
    }
}

public class CatvPortCapabilityService : ICapabilityService<CatvPortDetails>
{
    public bool OfCapability(CatvPortDetails port_details, FiberCapability capability)
    {
        ...
    }
}

public class PonPortCapabilityService : ICapabilityService<PonPortDetails>
{
    public bool OfCapability(PonPortDetails port_details, FiberCapability capability)
    {
        //If such kind of port does not have any capability, simply return false
        ...
    }
}

そして、これらの個々のサービスを使用して任意のポートの機能を伝えることができる別の実装を作成できます。

public class PortCapabilityService : ICapabilityService<PortDetails>
{
    private readonly ICapabilityService<PtpPortDetails> m_PtpPortCapabilityService;
    private readonly ICapabilityService<CatvPortDetails> m_CatvPortCapabilityService;
    private readonly ICapabilityService<PonPortDetails> m_PonPortCapabilityService;

    public PortCapabilityService(ICapabilityService<PtpPortDetails> ptp_port_capability_service, ICapabilityService<CatvPortDetails> catv_port_capability_service, ICapabilityService<PonPortDetails> pon_port_capability_service)
    {
        m_PtpPortCapabilityService = ptp_port_capability_service;
        m_CatvPortCapabilityService = catv_port_capability_service;
        m_PonPortCapabilityService = pon_port_capability_service;
    }

    public bool OfCapability(PortDetails port_details, FiberCapability capability)
    {
        PtpPortDetails ptp_port_details = port_details as PtpPortDetails;

        if (ptp_port_details != null)
            return m_PtpPortCapabilityService.OfCapability(ptp_port_details, capability);

        CatvPortDetails catv_port_details = port_details as CatvPortDetails;

        if (catv_port_details != null)
            return m_CatvPortCapabilityService.OfCapability(catv_port_details, capability);

        PonPortDetails pon_port_details = port_details as PonPortDetails;

        if (pon_port_details != null)
            return m_PonPortCapabilityService.OfCapability(pon_port_details, capability);

        throw new Exception("Unknown port type");
    }
}

ご覧のとおり、アルゴリズム クラスを除いて、どのクラスもポート ID を認識していません。はポート ID がなくてもジョブを実行できるため、機能を決定するクラスはポート ID を認識しません。

もう 1 つの注意点は、アルゴリズムを実行するたびに新しい機能サービスをインスタンス化する必要がないことです。これは、アルゴリズムを実行するたびに新しいインスタンスを作成する、質問で説明されている IInternetPort 実装とは対照的です。各インスタンスが異なる ID にバインドされているため、これを行ったと思います。

これらのクラスは依存性注入を使用します。それらを使用できるように構成する必要があります。これは、コンポジション ルートで行う必要があります。

このような構成にPure DIを使用する方法は次のとおりです。

IPortSelectionAlgorithm algorithm =
    new PortSelectionAlgorithm(
        new PortCapabilityService(
            new PtpPortCapabilityService(),
            new CatvPortCapabilityService(),
            new PonPortCapabilityService()));
于 2015-09-18T15:37:31.387 に答える
1

コードの匂いの兆候があると思います。PtpPortFactory の CreatePort では、キャストする代わりに PtpPortDetails (PortDetails から継承) を受け入れることができます。

いいえ、依存関係は実装ではなく抽象化にある必要があるため、問題ありません。したがって、この場合、PortDetails を渡すことは問題ありません。

私が見る匂いはここにあります:

public interface IPort
{
    int Id { get; }
}

public interface IInternetPort : IPort
{
    bool OfCapability(FiberCapability capability);
}

インターフェイスは基本的に動作を定義するために使用されます。そして、インターフェイスでプロパティを使用しているのは、私には疑わしいようです。

  • 継承はis-a関係を表します。
  • インターフェイスの実装は、can-do関係を表します。

ここでは、AbstractFactory パターンを扱っています。たとえば、宣言しBasePortFactoryていることを実行できる抽象ファクトリを持つことができます。IPortFactoryしたがってBasePortFactory、ファクトリメソッドから戻る必要があります。しかし、これもまた、ソリューションを設計する際の問題の選択です。

同様に、メソッドは、何かを使用するCreatePort場合に基づいて、戻り値の型を基本クラスまたはインターフェイスに公開する必要があります。is-acan-do

アップデート

このサンプルはあなたのシナリオには最適ではありませんが、これは私が共有したアイデアを表すためのものです:

public interface IInternetPort
{
    bool OfCapability(FiberCapability capability);
}

/// <summary>
/// This class can be a replacement of (IPort) interface. Each port is enabled for query via IInternetPort.
/// As a default behavior every port is not Internet enabled so OfCapability would return false.
/// Note: If you want you can still keep the IPort interface as Marker interface. 
/// /// </summary>
public abstract class Port : IInternetPort
{
    public int Id { get; private set; }

    public Port(int Id)
    {
        this.Id = Id;
    }

    public virtual bool OfCapability(FiberCapability capability)
    {
        // Default port is not internet capable
        return false; 
    }
}

/// <summary>
/// This class is-a <see cref="Port"/> and can provide capability checker.
/// Overiding the behavior of base for "OfCapability" would enable this port for internet.
/// </summary>
public class PtpPort : Port
{
    private readonly FiberCapability _capability;

    public PtpPort(int id, FiberCapability capability) : base(id)
    {
        _capability = capability;
    }

    public override bool OfCapability(FiberCapability capability)
    {
        return capability.Equals(_capability);
    }
}

/// <summary>
/// this test class doesn't need to implement or override OfCapability method
/// still it will be act like any other port.
/// | TestPort port = new TestPort(22);
/// | port.OfCapability(capability);
/// </summary>
public class TestPort : Port
{
    public TestPort(int id): base(id) { }
}

IPort ではなく Port を返すようにメソッドのシグネチャを変更する必要があるファクトリを次に示します。

public interface IPortFactory
{
    Port CreatePort(int id, PortDetails details);
}

public class PtpPortFactory : IPortFactory
{
    public Port CreatePort(int id, PortDetails details)
    {
        var ptpPortDetails = details as PtpPortDetails;
        if (ptpPortDetails == null)
        {
            throw new ArgumentException("Port details does not match ptp ports", "details");
        }

        return new PtpPort(id, FiberCapability.FromValue(ptpPortDetails.Capability));
    }
}

これで、この行に外部キャストは必要なくなりました。

var portsToSelectFrom = ports.Select(port => portFactory.CreatePort(port.Id, port.PortDetails)).ToList();

PS - これらのタイプの質問は、コード レビューまたはプログラマーで行う必要があります。

于 2015-09-14T17:58:55.720 に答える