web-dev-qa-db-ja.com

疎結合コードのインターフェースの使用

背景

特定の種類のハードウェアデバイスの使用に依存するプロジェクトがありますが、必要なことを実行する限り、そのハードウェアデバイスを誰が作成するかは重要ではありません。そうは言っても、同じことをするはずの2つのデバイスでも、同じ製造元以外のデバイスでは違いがあります。したがって、インターフェイスを使用して、関連するデバイスの特定のmake/modelからアプリケーションを分離し、代わりに、インターフェイスが最高のレベルの機能。これが私のアーキテクチャが次のようになると私が考えているものです:

  1. 1つのC#プロジェクトIDeviceでインターフェイスを定義します。
  2. 別のC#プロジェクトで定義されたライブラリに、デバイスを表すために使用される具象があります。
  3. 具象デバイスにIDeviceインターフェースを実装させます。
  4. IDeviceインターフェースには、GetMeasurementSetRangeなどのメソッドが含まれる場合があります。
  5. アプリケーションに具象に関する知識を持たせ、IDeviceデバイスを使用する(not implements)アプリケーションコードに具象を渡します。

アプリケーションに影響を与えずに、使用しているデバイスを変更できるため(これは時々発生するようです)、これが適切な方法であると確信しています。言い換えると、GetMeasurementまたはSetRangeの実装が実際にどのように具体的に機能するかは重要ではありません(デバイスのメーカーによって異なる場合があります)。

私の頭の中の唯一の疑問は、今やアプリケーションとデバイスの具象クラスの両方がIDeviceインターフェースを含むライブラリーに依存しているということです。しかし、それは悪いことですか?

また、デバイスとIDeviceが同じ名前空間にない限り、アプリケーションがデバイスを認識する必要がない方法もわかりません。

質問

これは、アプリケーションとそれが使用するデバイスとの間の依存関係を切り離すためのインターフェースを実装するための適切なアプローチのように見えますか?

10
Snoop

分離されたソフトウェアがどのように機能するかをかなり理解できたと思います:)

私の心の中で唯一の疑問は、今やアプリケーションとデバイスの具象クラスの両方が、IDeviceインターフェイスを含むライブラリに依存していることです。しかし、それは悪いことですか?

する必要はありません!

また、デバイスとIDeviceが同じ名前空間にない限り、アプリケーションがデバイスについて知る必要がない方法もわかりません。

これらすべての懸念はプロジェクト構造で対処できます。

私が通常行う方法:

  • すべての抽象的なものをCommonプロジェクトに入れます。 MyBiz.Project.Commonのようなもの。他のプロジェクトはそれを自由に参照できますが、他のプロジェクトを参照しない場合があります。
  • 抽象化の具体的な実装を作成するとき、それを別のプロジェクトに配置しました。 MyBiz.Project.Devices.TemperatureSensorsのようなもの。このプロジェクトはCommonプロジェクトを参照します。
  • 次に、アプリケーションへのエントリポイントであるClientプロジェクト(MyBiz.Project.Desktopなど)があります。起動時に、アプリケーションはBootstrappingプロセスを経て、抽象化/コンクリート実装マッピングを構成します。ここでIDevicesWaterTemperatureSensorのような具体的なIRCameraTemperatureSensorをインスタンス化したり、ファクトリーやIoCコンテナーなどのサービスを構成して、後で適切な具象タイプをインスタンス化したりできます。

ここで重要なのは、Clientプロジェクトのみが、抽象Commonプロジェクトとすべての具象実装プロジェクトの両方を認識する必要があるということです。 Bootstrapコードへのabstract-> concreteマッピングを制限することにより、アプリケーションの残りの部分が具体的な型に気付かずに気づかないようにすることができます。

疎結合コードftw :)

5
MetaFight

はい、それは正しいアプローチのようです。いいえ、特に実装を制御している場合は、アプリケーションとデバイスライブラリがインターフェイスに依存することは悪いことではありません。

何らかの理由でデバイスがインターフェースを常に実装するとは限らないことが懸念される場合は、アダプターパターンを利用して、デバイスの具体的な実装をインターフェースに適合させることができます。

編集

5番目の懸念に対処するには、次のような構造を考えてください(私はあなたがデバイスの定義を制御していると想定しています)。

コアライブラリがあります。その中には、IDeviceと呼ばれるインターフェースがあります。

デバイスライブラリには、コアライブラリへの参照があり、すべてIDeviceを実装する一連のデバイスを定義しています。また、さまざまな種類のIDeviceを作成する方法を知っているファクトリもあります。

アプリケーションには、コアライブラリとデバイスライブラリへの参照を含めます。これで、アプリケーションはファクトリを使用して、IDeviceインターフェイスに準拠するオブジェクトのインスタンスを作成します。

これは、懸念に対処するための多くの可能な方法の1つです。

例:

namespace Core
{
    public interface IDevice { }
}


namespace Devices
{
    using Core;

    class DeviceOne : IDevice { }

    class DeviceTwo : IDevice { }

    public class Factory
    {
        public IDevice CreateDeviceOne()
        {
            return new DeviceOne();
        }

        public IDevice CreateDeviceTwo()
        {
            return new DeviceTwo();
        }
    }
}

// do not implement IDevice
namespace ThirdrdPartyDevices
{

    public class ThirdPartyDeviceOne  { }

    public class ThirdPartyDeviceTwo  { }

}

namespace DeviceAdapters
{
    using Core;
    using ThirdPartyDevices;

    class ThirdPartyDeviceAdapterOne : IDevice
    {
        private ThirdPartyDeviceOne _deviceOne;

        // use the third party device to adapt to the interface
    }

    class ThirdPartyDeviceAdapterTwo : IDevice
    {
        private ThirdPartyDeviceTwo _deviceTwo;

        // use the third party device to adapt to the interface
    }

    public class AdapterFactory
    {
        public IDevice CreateThirdPartyDeviceAdapterOne()
        {
            return new ThirdPartyDeviceAdapterOne();
        }

        public IDevice CreateThirdPartyDeviceAdapterTwo()
        {
            return new ThirdPartyDeviceAdapterTwo();
        }
    }
}

namespace Application
{
    using Core;
    using Devices;
    using DeviceAdapters;

    class App
    {
        void RunInHouse()
        {
            var factory = new Factory();
            var devices = new List<IDevice>() { factory.CreateDeviceOne(), factory.CreateDeviceTwo() };
            foreach (var device in devices)
            {
                // call IDevice  methods.
            }
        }

        void RunThirdParty()
        {
            var factory = new AdapterFactory();
            var devices = new List<IDevice>() { factory.CreateThirdPartyDeviceAdapterOne(), factory.CreateThirdPartyDeviceAdapterTwo() };
            foreach (var device in devices)
            {
                // call IDevice  methods.
            }
        }
    }
}
3
Price Jones

私の頭の中の唯一の疑問は、今やアプリケーションとデバイスの具象クラスの両方が、IDeviceインターフェイスを含むライブラリに依存していることです。しかし、それは悪いことですか?

逆に考える必要があります。この方法を使用しない場合、アプリケーションはさまざまなデバイス/実装のすべてについて知る必要があります。覚えておくべきことは、そのインターフェイスを変更すると、アプリケーションが既に一般に公開されている場合、アプリケーションが壊れる可能性があるため、そのインターフェイスを注意深く設計する必要があるということです。

これは、アプリケーションとそれが使用するデバイスとの間の依存関係を切り離すためのインターフェースを実装するための適切なアプローチのように見えますか?

単にはい

コンクリートが実際にアプリに到達する方法

アプリケーションは、実行時に具象Assembly(dll)をロードできます。参照: https://stackoverflow.com/questions/465488/can-i-load-a-net-Assembly-at-runtime-and-instantiate-a-type-knowing-only-the-na

このような「ドライバー」を動的にアンロード/ロードする必要がある場合は、アセンブリを別の AppDomain にロードする必要があります。

1
Heslacher