결합도가 너무 높습니다. 이 클래스를 더 잘 디자인하는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/189156

  •  06-07-2019
  •  | 
  •  

문제

내 코드에서 FxCop을 실행하면 다음 경고가 표시됩니다.

Microsoft.유지관리성:'foobar.ctor는 9 개의 다른 네임 스페이스에서 99 개의 다른 유형과 결합됩니다.클래스 커플 링을 줄이기위한 방법을 다시 작성하거나 리팩터링하거나, 방법을 단단히 결합한 다른 유형 중 하나로 이동하는 것을 고려하십시오.40을 초과하는 클래스 커플 링은 유지 관리가 열악하고 40에서 30 사이의 클래스 커플 링은 적당한 유지 관리 가능성을 나타내고 30 미만의 클래스 커플 링은 양호한 유지 보수성을 나타냅니다.

내 수업은 서버의 모든 메시지에 대한 랜딩 존입니다.서버는 다양한 EventArgs 유형의 메시지를 보낼 수 있습니다.

public FooBar()
{
    var messageHandlers = new Dictionary<Type, Action<EventArgs>>();
    messageHandlers.Add(typeof(YouHaveBeenLoggedOutEventArgs), HandleSignOut);
    messageHandlers.Add(typeof(TestConnectionEventArgs), HandleConnectionTest);
    // ... etc for 90 other types
}

"HandleSignOut" 및 "HandleConnectionTest" 메서드에는 코드가 거의 없습니다.일반적으로 작업을 다른 클래스의 함수에 전달합니다.

낮은 결합도를 사용하여 이 수업을 어떻게 더 좋게 만들 수 있나요?

도움이 되었습니까?

해결책

그들이 관심있는 이벤트를 위해 작업 등록을하는 수업을받습니다 ... 이벤트 브로커 무늬.

class EventBroker {
   private Dictionary<Type, Action<EventArgs>> messageHandlers;

   void Register<T>(Action<EventArgs> subscriber) where T:EventArgs {
      // may have to combine delegates if more than 1 listener
      messageHandlers[typeof(T)] = subscriber; 
   }

   void Send<T>(T e) where T:EventArgs {
      var d = messageHandlers[typeof(T)];
      if (d != null) {
         d(e);
      }
   }
}

다른 팁

Spring.NET과 같은 일종의 IoC 프레임워크를 사용하여 사전을 삽입할 수도 있습니다.이렇게 하면 새 메시지 유형을 받은 경우 이 중앙 허브를 다시 컴파일할 필요가 없습니다. 구성 파일만 변경하면 됩니다.


오랫동안 기다려온 예:

example이라는 새 콘솔 앱을 만들고 다음을 추가합니다.

using System;
using System.Collections.Generic;
using Spring.Context.Support;

namespace Example
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            MessageBroker broker = (MessageBroker) ContextRegistry.GetContext()["messageBroker"];
            broker.Dispatch(null, new Type1EventArgs());
            broker.Dispatch(null, new Type2EventArgs());
            broker.Dispatch(null, new EventArgs());
        }
    }

    public class MessageBroker
    {
        private Dictionary<Type, object> handlers;

        public Dictionary<Type, object> Handlers
        {
            get { return handlers; }
            set { handlers = value; }
        }

        public void Dispatch<T>(object sender, T e) where T : EventArgs
        {
            object entry;
            if (Handlers.TryGetValue(e.GetType(), out entry))
            {
                MessageHandler<T> handler = entry as MessageHandler<T>;
                if (handler != null)
                {
                    handler.HandleMessage(sender, e);
                }
                else
                {
                    //I'd log an error here
                    Console.WriteLine("The handler defined for event type '" + e.GetType().Name + "' doesn't implement the correct interface!");
                }
            }
            else
            {
                //I'd log a warning here
                Console.WriteLine("No handler defined for event type: " + e.GetType().Name);
            }
        }
    }

    public interface MessageHandler<T> where T : EventArgs
    {
        void HandleMessage(object sender, T message);
    }

    public class Type1MessageHandler : MessageHandler<Type1EventArgs>
    {
        public void HandleMessage(object sender, Type1EventArgs args)
        {
            Console.WriteLine("Type 1, " + args.ToString());
        }
    }

    public class Type2MessageHandler : MessageHandler<Type2EventArgs>
    {
        public void HandleMessage(object sender, Type2EventArgs args)
        {
            Console.WriteLine("Type 2, " + args.ToString());
        }
    }

    public class Type1EventArgs : EventArgs {}

    public class Type2EventArgs : EventArgs {}
}

app.config 파일:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
    </sectionGroup>
  </configSections>

  <spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmlns="http://www.springframework.net">

      <object id="messageBroker" type="Example.MessageBroker, Example">
        <property name="handlers">
          <dictionary key-type="System.Type" value-type="object">
            <entry key="Example.Type1EventArgs, Example" value-ref="type1Handler"/>
            <entry key="Example.Type2EventArgs, Example" value-ref="type2Handler"/>
          </dictionary>
        </property>
      </object>
      <object id="type1Handler" type="Example.Type1MessageHandler, Example"/>
      <object id="type2Handler" type="Example.Type2MessageHandler, Example"/>
    </objects>
  </spring>
</configuration>

산출:

Type 1, Example.Type1EventArgs
Type 2, Example.Type2EventArgs
No handler defined for event type: EventArgs

보시다시피, MessageBroker 핸들러에 대해 모르고 핸들러도 알지 못합니다. MessageBroker.모든 매핑은 app.config 파일에서 수행되므로 새 이벤트 유형을 처리해야 하는 경우 이를 구성 파일에 추가할 수 있습니다.이는 다른 팀이 이벤트 유형과 핸들러를 정의하는 경우 특히 유용합니다. dll에서 해당 항목을 컴파일하고 배포에 추가한 후 매핑을 추가하기만 하면 됩니다.

Dictionary에는 대신 object 유형의 값이 있습니다. MessageHandler<> 실제 핸들러는 캐스팅될 수 없기 때문에 MessageHandler<EventArgs>, 그래서 나는 그 문제를 조금 해킹해야했습니다.솔루션이 여전히 깨끗하고 매핑 오류를 잘 처리한다고 생각합니다.이 프로젝트에서는 Spring.Core.dll도 참조해야 합니다.도서관을 찾으실 수 있습니다 여기, 그리고 문서 여기.그만큼 의존성 주입 장 이것과 관련이 있습니다.또한 이를 위해 Spring.NET을 사용해야 할 이유가 없습니다. 여기서 중요한 아이디어는 종속성 주입입니다.어떻게든 브로커에게 a 유형의 메시지를 x로 보내도록 지시해야 하며, 종속성 주입을 위해 IoC 컨테이너를 사용하는 것은 브로커가 x에 대해 알지 못하도록 하는 좋은 방법이며 그 반대의 경우도 마찬가지입니다.

IoC 및 DI와 관련된 다른 SO 질문:

나머지 코드는 보이지 않지만 훨씬 적은 수의 이벤트 arg 클래스를 만들려고합니다. 대신 포함 된 데이터와/또는 나중에 처리하는 방법에서 서로 유사한 몇 가지를 생성하고 정확한 유형의 이벤트 유형을 알려주는 필드를 추가하십시오 (아마도 열거를 사용해야 함).

이상적으로는이 생성자가 훨씬 더 읽기 쉽게 만들뿐만 아니라 메시지가 처리되는 방식도 만들 것입니다 (단일 이벤트 핸들러에서 유사한 방식으로 처리되는 그룹 메시지).

아마도 각 메시지마다 다른 클래스를 갖는 대신 메시지를 식별하는 플래그를 사용하십시오.

이렇게하면 메시지의 수를 크게 줄이고 유지 관리 가능성이 높아집니다. 내 생각에 대부분의 메시지 클래스는 거의 차이가 없다는 것입니다.

나머지 아키텍처는 알려지지 않았기 때문에 이것을 공격하는 추가 방법을 선택하기가 어렵습니다.

예를 들어 Windows를 보면 기본적으로 던질 수있는 각 메시지를 처리하는 방법을 알지 못합니다. 대신, 기본 메시지 처리기는 기본 스레드와 함께 콜백 기능을 등록합니다.

당신은 비슷한 접근 방식을 취할 수 있습니다. 각 메시지 클래스는 스스로 처리하는 방법을 알아야하며 더 큰 응용 프로그램에 스스로 등록 할 수 있습니다. 이것은 코드를 크게 단순화하고 단단한 커플 링을 제거해야합니다.

분명히 당신은 파견 메커니즘이 필요합니다.받은 이벤트에 따라 다른 코드를 실행하려고합니다.

유형 시스템을 사용하여 이벤트를 식별하는 것처럼 보이지만 실제로 다형성을 지원하기위한 것 같습니다. Chris Lively에서 알 수 있듯이 (유형 시스템을 남용하지 않고) 열거를 사용하여 메시지를 식별 할 수 있습니다.

또는 유형 시스템의 전력을 수용하고 모든 유형의 이벤트가 등록되는 레지스트리 객체를 만들 수 있습니다 (정적 인스턴스, 구성 파일 또는 그대로). 그런 다음 책임 패턴의 체인을 사용하여 적절한 핸들러를 찾을 수 있습니다. 핸들러는 처리 자체를 수행하거나 공장 일 수 있으며 이벤트를 처리하는 객체를 만듭니다.

후자의 방법은 약간 지정되고 과도하게 된 것처럼 보이지만 99 개의 이벤트 유형 (이미)의 경우 나에게 적합한 것 같습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top