느슨하게 결합 된 코드에 인터페이스 사용


10

배경

특정 유형의 하드웨어 장치의 사용에 의존하는 프로젝트가 있지만 필요한 작업을 수행하는 한 누가 하드웨어 장치를 만드는지는 중요하지 않습니다. 그렇게 말하면, 같은 일을하도록되어있는 두 장치조차도 같은 제조업체가 만들지 않으면 차이가있을 것입니다. 따라서 인터페이스를 사용하여 응용 프로그램을 관련된 특정 제조업체 / 모델 과 분리 하고 대신 인터페이스가 최상위 기능을 다루도록 하려고 합니다. 내 아키텍처가 다음과 같이 보일 것이라고 생각합니다.

  1. 하나의 C # 프로젝트에서 인터페이스를 정의하십시오 IDevice.
  2. 장치를 나타내는 데 사용될 다른 C # 프로젝트에 정의 된 라이브러리에 구체적인 내용이 있습니다.
  3. 콘크리트 장치가 IDevice인터페이스를 구현하도록하십시오 .
  4. IDevice인터페이스는 같은 방법있을 수 있습니다 GetMeasurement또는 SetRange.
  5. 응용 프로그램에 콘크리트에 대한 정보를 제공 하고 장치 를 사용하는 ( 구현하지 않는 ) 응용 프로그램 코드로 콘크리트를 전달하십시오 IDevice.

이것이 올바른 방법이라고 확신합니다. 응용 프로그램에 영향을 미치지 않고 사용되는 장치를 변경할 수 있기 때문입니다 (때로는 발생하는 것처럼 보입니다). 즉, 장치의 제조업체마다 다를 수 있으므로 콘크리트 의 구현 GetMeasurement또는 SetRange실제 구현 방식은 중요하지 않습니다 .

내 생각에 유일한 의심은 이제 응용 프로그램과 장치의 구체적인 클래스가 모두 IDevice인터페이스 가 포함 된 라이브러리에 달려 있다는 것 입니다. 그러나 그것은 나쁜 것입니까?

또한 장치 IDevice가 동일한 네임 스페이스에 있지 않으면 응용 프로그램이 장치에 대해 알아야 할 필요가 없습니다 .

질문

내 응용 프로그램과 사용하는 장치 간의 종속성을 분리하기 위해 인터페이스를 구현하는 올바른 방법처럼 보입니까?


이것은 절대적으로 올바른 방법입니다. 당신은 본질적으로 장치 드라이버를 프로그래밍하고 있으며, 이것은 정당한 이유와 함께 전통적으로 장치 드라이버가 작성되는 방식입니다. 장치의 기능을 사용하려면 최소한 추상적 인 방식으로 이러한 기능을 알고있는 코드에 의존해야한다는 사실을 피할 수 없습니다.
Kilian Foth

@KilianFoth 그래, 나는 느낌이 들었습니다. 내 질문에 한 부분을 추가하는 것을 잊었습니다. # 5를 참조하십시오.
Snoop

답변:


5

분리 된 소프트웨어의 작동 방식에 대해 잘 알고 있다고 생각합니다. :)

내 생각에 유일한 의심은 이제 응용 프로그램과 장치의 구체적인 클래스가 모두 IDevice 인터페이스를 포함하는 라이브러리에 의존한다는 것입니다. 그러나 그것은 나쁜 것입니까?

꼭 그럴 필요는 없습니다!

또한 장치와 IDevice가 동일한 네임 스페이스에 있지 않으면 응용 프로그램이 장치에 대해 알아야 할 필요가 없습니다.

프로젝트 구조 와 관련된 모든 문제를 해결할 수 있습니다 .

내가 일반적으로하는 방식 :

  • 내 추상적 인 것들을 모두 Common프로젝트 에 넣습니다 . 같은 것 MyBiz.Project.Common. 다른 프로젝트는 자유롭게 참조 할 수 있지만 다른 프로젝트를 참조하지 않을 수도 있습니다.
  • 추상화의 구체적인 구현을 만들 때 별도의 프로젝트에 넣었습니다. 같은 것 MyBiz.Project.Devices.TemperatureSensors. 이 프로젝트는 프로젝트를 참조 할 것 Common입니다.
  • 그런 다음 Client내 응용 프로그램의 진입 점 인 프로젝트 ( MyBiz.Project.Desktop)가 있습니다. 시작할 때 응용 프로그램은 부트 스트랩 프로세스를 거쳐 추상화 / 콘크리트 구현 매핑을 구성합니다. 내 콘크리트를 인스턴스화 할 수 IDevices처럼 WaterTemperatureSensor하고 IRCameraTemperatureSensor여기에, 또는 내가 나중에 나에게 맞는 구체적인 유형을 인스턴스화하는 공장 또는 IoC 컨테이너 등의 서비스를 구성 할 수 있습니다.

여기서 중요한 것은 Client프로젝트 만 추상 Common프로젝트와 모든 구체적인 구현 프로젝트 를 모두 알고 있어야한다는 것입니다 . 추상-> 콘크리트 매핑을 Bootstrap 코드로 제한하면 나머지 응용 프로그램이 콘크리트 유형을 행복하게 인식하지 못할 수 있습니다.

느슨하게 연결된 코드 ftw :)


2
DI는 여기에서 자연스럽게 따라갑니다. 그것은 주로 프로젝트 / 코드 구조 문제이기도합니다. IoC 컨테이너가 있으면 DI에 도움이 될 수 있지만 필수 구성 요소는 아닙니다. 종속성을 수동으로 주입하여 DI를 달성 할 수 있습니다!
MetaFight

1
@StevieV 예, 응용 프로그램 내의 Foo 객체에 IDevice가 필요한 경우 new Foo(new DeviceA());Foo 인스턴스화 DeviceA 자체 ( private IDevice idevice = new DeviceA();) 내에 개인 필드를 두지 않고 생성자 ( ) 를 통해 주입하는 것처럼 종속성 반전이 간단 할 수 있습니다. 푸 첫 번째 경우에 위해 기기를 인식하지 못합니다
anotherdave

2
@ anotherdave 당신과 MetaFight 입력은 매우 도움이되었습니다.
스눕

1
@StevieV 당신이 시간을 가졌을 때 , Spring (또는 다른 어떤 것보다 Inversion of Control / Depency Injection 프레임 워크와는 다른 개념 인 Dependency Inversion에 대한 좋은 소개 가있다.
C♯

1
@anotherdave 확실히 그것을 확인 계획, 다시 감사합니다.
스눕

3

예, 그것은 올바른 접근법처럼 보입니다. 아니요, 특히 구현을 제어하는 ​​경우 응용 프로그램과 장치 라이브러리가 인터페이스에 의존하는 것은 나쁘지 않습니다.

어떤 이유로 든 장치가 항상 인터페이스를 구현하지 않을 수도있는 경우 어댑터 패턴을 사용하고 장치의 구체적인 구현을 인터페이스에 적용 할 수 있습니다.

편집하다

다섯 번째 관심사를 해결할 때 다음과 같은 구조를 생각하십시오 (장치 정의를 제어한다고 가정합니다).

핵심 라이브러리가 있습니다. IDevice라는 인터페이스입니다.

장치 라이브러리에는 핵심 라이브러리에 대한 참조가 있으며 IDevice를 모두 구현하는 일련의 장치를 정의했습니다. 또한 다른 종류의 IDevice를 작성하는 방법을 알고있는 팩토리가 있습니다.

응용 프로그램에는 핵심 라이브러리 및 장치 라이브러리에 대한 참조가 포함됩니다. 응용 프로그램은 이제 팩토리를 사용하여 IDevice 인터페이스에 맞는 객체 인스턴스를 만듭니다.

이것은 귀하의 우려를 해결하는 많은 가능한 방법 중 하나입니다.

예 :

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.
            }
        }
    }
}

이 구현으로 콘크리트는 어디로 갈까요?
스눕

내가하고자하는 일에 대해서만 말할 수 있습니다. 장치를 제어하는 ​​경우에도 구체적인 장치를 자체 라이브러리에 배치합니다. 장치를 제어하지 않으면 어댑터에 대한 다른 라이브러리를 작성합니다.
Price Jones

응용 프로그램이 필요한 특정 콘크리트에 어떻게 액세스 할 수 있습니까?
스눕

한 가지 해결책은 추상 팩토리 패턴을 사용하여 IDevice를 작성하는 것입니다.
Price Jones

1

내 생각에 유일한 의심은 이제 응용 프로그램과 장치의 구체적인 클래스가 모두 IDevice 인터페이스를 포함하는 라이브러리에 의존한다는 것입니다. 그러나 그것은 나쁜 것입니까?

다른 방법으로 생각해야합니다. 이 방법으로 가지 않으면 응용 프로그램은 모든 다른 장치 / 구현에 대해 알아야합니다. 명심해야 할 것은 해당 인터페이스를 변경하면 응용 프로그램이 이미 거친 경우 응용 프로그램이 중단 될 수 있으므로 인터페이스를 신중하게 디자인해야한다는 것입니다.

내 응용 프로그램과 사용하는 장치 간의 종속성을 분리하기 위해 인터페이스를 구현하는 올바른 방법처럼 보입니까?

간단하게 그렇습니다

콘크리트가 실제로 앱에 도달하는 방법

응용 프로그램은 런타임에 콘크리트 어셈블리 (dll)를로드 할 수 있습니다. 참조 : https : //.com/questions/465488/can-i-load-a-net-assembly-at-runtime-and-instantiate-a-type-knowing-only-the-na

이러한 "드라이버"를 동적으로 언로드 /로드해야하는 경우 별도의 AppDomain에 어셈블리를로드해야합니다 .


감사합니다. 코딩을 처음 시작할 때 인터페이스에 대해 더 많이 알고 싶습니다.
스눕

죄송합니다. 한 부분을 추가하는 것을 잊었습니다 (콘크리트가 실제로 앱에 표시되는 방식). 당신은 그것을 해결할 수 있습니까? # 5를 참조하십시오.
스눕

실제로 런타임에 DLL을로드하여 앱을 이런 식으로 수행합니까?
스눕

예를 들어 구체적인 클래스를 모르는 경우 예. 장치 공급 업체가 IDevice자신의 장치를 앱과 함께 사용할 수 있도록 인터페이스 를 사용하기로 결정했다고 가정하면 다른 방법으로는 IMO가 없을 것입니다.
Heslacher
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.