[c#] 객체 지향 프로그래밍-변수에 따라 약간 다른 프로세스에서 중복을 피하는 방법

현재 작업에서 상당히 많이 나오는 것은 일어날 필요가있는 일반화 된 프로세스가 있다는 것입니다. 그러나 그 프로세스의 이상한 부분은 특정 변수의 값에 따라 약간 다르게 일어날 필요가 있습니다. 이것을 처리하는 가장 우아한 방법이 무엇인지 확실히 확신하십시오.

우리가 다루는 국가에 따라 약간 다른 방식으로 일을하는 예를 사용하겠습니다.

그래서 나는 수업을 가지고 있습니다 Processor.

public class Processor
{
    public string Process(string country, string text)
    {
        text.Capitalise();

        text.RemovePunctuation();

        text.Replace("é", "e");

        var split = text.Split(",");

        string.Join("|", split);
    }
}

일부 국가에서는 이러한 작업 중 일부만 수행하면됩니다. 예를 들어, 6 개국 만이 대문자 사용 단계를 요구합니다. 분할 할 캐릭터는 국가에 따라 다를 수 있습니다. 'e'국가에 따라 악센트를 교체해야 할 수도 있습니다.

분명히 다음과 같이하면 해결할 수 있습니다.

public string Process(string country, string text)
{
    if (country == "USA" || country == "GBR")
    {
        text.Capitalise();
    }

    if (country == "DEU")
    {
        text.RemovePunctuation();
    }

    if (country != "FRA")
    {
        text.Replace("é", "e");
    }

    var separator = DetermineSeparator(country);
    var split = text.Split(separator);

    string.Join("|", split);
}

그러나 세계에서 가능한 모든 국가를 다룰 때는 매우 번거로워집니다. 그럼에도 불구하고,이 if문장은 논리를 읽기 어렵게 만듭니다 (적어도 예보다 복잡한 방법을 상상한다면), 순환 복잡성은 매우 빠르게 시작됩니다.

그래서 지금 나는 이런 식으로 뭔가를하고 있습니다 :

public class Processor
{
    CountrySpecificHandlerFactory handlerFactory;

    public Processor(CountrySpecificHandlerFactory handlerFactory)
    {
        this.handlerFactory = handlerFactory;
    }

    public string Process(string country, string text)
    {
        var handlers = this.handlerFactory.CreateHandlers(country);
        handlers.Capitalier.Capitalise(text);

        handlers.PunctuationHandler.RemovePunctuation(text);

        handlers.SpecialCharacterHandler.ReplaceSpecialCharacters(text);

        var separator = handlers.SeparatorHandler.DetermineSeparator();
        var split = text.Split(separator);

        string.Join("|", split);
    }
}

처리기 :

public class CountrySpecificHandlerFactory
{
    private static IDictionary<string, ICapitaliser> capitaliserDictionary
                                    = new Dictionary<string, ICapitaliser>
    {
        { "USA", new Capitaliser() },
        { "GBR", new Capitaliser() },
        { "FRA", new ThingThatDoesNotCapitaliseButImplementsICapitaliser() },
        { "DEU", new ThingThatDoesNotCapitaliseButImplementsICapitaliser() },
    };

    // Imagine the other dictionaries like this...

    public CreateHandlers(string country)
    {
        return new CountrySpecificHandlers
        {
            Capitaliser = capitaliserDictionary[country],
            PunctuationHanlder = punctuationDictionary[country],
            // etc...
        };
    }
}

public class CountrySpecificHandlers
{
    public ICapitaliser Capitaliser { get; private set; }
    public IPunctuationHanlder PunctuationHanlder { get; private set; }
    public ISpecialCharacterHandler SpecialCharacterHandler { get; private set; }
    public ISeparatorHandler SeparatorHandler { get; private set; }
}

똑같이 나는 정말로 마음에 들지 않습니다. 로직은 여전히 ​​모든 팩토리 생성에 의해 가려져 있으며 원래 방법 만보고 “GBR”프로세스가 실행될 때 어떤 일이 발생하는지 확인할 수 없습니다. 또한 스타일 등에서 많은 클래스 (이보다 복잡한 예제에서)를 생성하게됩니다. 즉 GbrPunctuationHandler, UsaPunctuationHandler구두점 중에 발생할 수있는 모든 가능한 동작을 파악하려면 여러 클래스를 살펴 봐야합니다. 손질. 분명히 나는 ​​10 억 개의 if진술을 가진 거대한 클래스 하나를 원하지 않지만 약간 다른 논리를 가진 20 개의 클래스도 어색합니다.

기본적으로 나는 일종의 OOP 매듭에 빠져 있고 그것을 풀기위한 좋은 방법을 모른다고 생각합니다. 이 유형의 프로세스에 도움이 될 패턴이 있는지 궁금합니다.



답변

모든 옵션을 한 클래스로 캡슐화하는 것이 좋습니다.

public class ProcessOptions
{
  public bool Capitalise { get; set; }
  public bool RemovePunctuation { get; set; }
  public bool Replace { get; set; }
  public char ReplaceChar { get; set; }
  public char ReplacementChar { get; set; }
  public char JoinChar { get; set; }
  public char SplitChar { get; set; }
}

Process메소드에 전달하십시오 .

public string Process(ProcessOptions options, string text)
{
  if(options.Capitalise)
    text.Capitalise();

  if(options.RemovePunctuation)
    text.RemovePunctuation();

  if(options.Replace)
    text.Replace(options.ReplaceChar, options.ReplacementChar);

  var split = text.Split(options.SplitChar);

  string.Join(options.JoinChar, split);
}


답변

.NET 프레임 워크가 이러한 종류의 문제를 처리하기 시작했을 때 모든 것을 모델링하지 않았습니다 string. 예를 들어 CultureInfo클래스가 있습니다 .

특정 문화권 (관리되지 않는 코드 개발을위한 로캘이라고 함)에 대한 정보를 제공합니다. 정보에는 문화 이름, 쓰기 시스템, 사용 된 달력, 문자열 정렬 순서 및 날짜 및 숫자 형식이 포함됩니다.

이제이 클래스에는 필요한 특정 기능이 포함되어 있지 않을 수도 있지만 비슷한 것을 만들 수 있습니다. 그런 다음 Process방법 을 변경하십시오 .

public string Process(CountryInfo country, string text)

그러면 CountryInfo수업에bool RequiresCapitalization 당신의 도움 등 재산, Process방법은 적절하게 처리를 지시합니다.


답변

아마도 당신은 Processor나라 당 하나를 가질 수 있습니까?

public class FrProcessor : Processor {
    protected override string Separator => ".";

    protected override string ProcessSpecific(string text) {
        return text.Replace("é", "e");
    }
}

public class UsaProcessor : Processor {
    protected override string Separator => ",";

    protected override string ProcessSpecific(string text) {
        return text.Capitalise().RemovePunctuation();
    }
}

그리고 처리의 공통 부분을 처리하는 하나의 기본 클래스 :

public abstract class Processor {
    protected abstract string Separator { get; }

    protected virtual string ProcessSpecific(string text) { }

    private string ProcessCommon(string text) {
        var split = text.Split(Separator);
        return string.Join("|", split);
    }

    public string Process(string text) {
        var s = ProcessSpecific(text);
        return ProcessCommon(s);
    }
}

또한 리턴 유형은 작성한대로 컴파일되지 않기 때문에 재 작업해야합니다. 때로는 string메소드가 아무것도 리턴하지 않습니다.


답변

당신은 Process방법 으로 공통 인터페이스를 만들 수 있습니다 …

public interface IProcessor
{
    string Process(string text);
}

그런 다음 각 국가마다 구현합니다 …

public class Processors
{
    public class GBR : IProcessor
    {
        public string Process(string text)
        {
            return $"{text} (processed with GBR rules)";
        }
    }

    public class FRA : IProcessor
    {
        public string Process(string text)
        {
            return $"{text} (processed with FRA rules)";
        }
    }
}

그런 다음 각 국가 관련 클래스를 인스턴스화하고 실행하는 일반적인 방법을 만들 수 있습니다.

// also place these in the Processors class above
public static IProcessor CreateProcessor(string country)
{
    var typeName = $"{typeof(Processors).FullName}+{country}";
    var processor = (IProcessor)Assembly.GetAssembly(typeof(Processors)).CreateInstance(typeName);
    return processor;
}

public static string Process(string country, string text)
{
    var processor = CreateProcessor(country);
    return processor?.Process(text);
}

그런 다음 프로세서를 만들고 사용하면됩니다.

// create a processor object for multiple use, if needed...
var processorGbr = Processors.CreateProcessor("GBR");
Console.WriteLine(processorGbr.Process("This is some text."));

// create and use a processor for one-time use
Console.WriteLine(Processors.Process("FRA", "This is some more text."));

작동하는 닷넷 바이올린 예제는 다음과 같습니다.

모든 국가 별 처리는 각 국가 클래스에 배치합니다. 모든 실제 개별 메소드에 대해 공통 클래스 (처리 클래스에서)를 작성하여 각 국가 별 프로세서가 각 국가 클래스의 코드를 복사하는 대신 다른 공통 호출 목록이됩니다.

참고 : 추가해야합니다 …

using System.Assembly;

정적 메소드가 국가 클래스의 인스턴스를 작성하기 위해.


답변

몇 가지 버전 전에 C # swtich가 주어졌습니다. 패턴 일치를 완벽하게 지원했습니다 . 따라서 “여러 국가 일치”사례를 쉽게 수행 할 수 있습니다. 여전히 통과 능력은 없지만 하나의 입력으로 여러 경우를 패턴 일치로 일치시킬 수 있습니다. 스팸 일 경우 좀 더 명확하게 만들 수 있습니다.

Npw는 일반적으로 스위치를 컬렉션으로 교체 할 수 있습니다. 대리인과 사전을 사용해야합니다. 프로세스를 대체 할 수 있습니다.

public delegate string ProcessDelegate(string text);

그런 다음 사전을 만들 수 있습니다.

var Processors = new Dictionary<string, ProcessDelegate>(){
  { "USA", EnglishProcessor },
  { "GBR", EnglishProcessor },
  { "DEU", GermanProcessor }
}

functionNames를 사용하여 델리게이트를 전달했습니다. 그러나 Lambda 구문을 사용하여 전체 코드를 제공 할 수 있습니다. 그렇게하면 다른 큰 컬렉션처럼 전체 컬렉션을 숨길 수 있습니다. 그리고 코드는 간단한 조회가됩니다 :

ProcessDelegate currentProcessor = Processors[country];
string processedString = currentProcessor(country);

그것들은 거의 두 가지 옵션입니다. 일치하는 문자열 대신 열거 사용을 고려할 수도 있지만 약간의 세부 사항입니다.


답변

아마 (귀하의 유스 케이스의 세부 사항에 따라) Country 문자열 대신 “실제”객체가 될 것입니다. 키워드는 “다형성”입니다.

기본적으로 다음과 같습니다.

public interface Country {
   string Process(string text);
}

그런 다음 필요한 국가를위한 특수 국가를 만들 수 있습니다. 참고 : Country모든 국가에 대해 객체 를 만들 필요는 없으며 LatinlikeCountry, 또는 가질 수도 GenericCountry있습니다. 여기서 수행해야 할 작업을 수집하고 다음과 같이 다른 사람을 재사용 할 수도 있습니다.

public class France {
   public string Process(string text) {
      return new GenericCountry().process(text)
         .replace('a', 'b');
   }
}

또는 비슷합니다. Country실제로있을 수 있습니다Language 사용 사례에 대해 잘 모르겠지만 요점을 알 수 있습니다.

또한 물론 방법은 Process()실제로 필요한 일 이 아니 어야합니다. 좋아 Words()하거나 무엇이든.


답변

당신은 자신의 문화에 대해 알고있는 무언가에 책임을 져야합니다. 따라서 다른 답변에서 위에서 언급 한 것처럼 Country 또는 CultureInfo 유형 구문을 사용하거나 만드십시오.

그러나 일반적으로 근본적으로 문제는 ‘프로세서’와 같은 절차 적 구성을 취하여 OO에 적용하는 것입니다. OO는 소프트웨어의 비즈니스 또는 문제 영역에서 실제 개념을 나타내는 것입니다. 프로세서는 소프트웨어 자체와는 별도로 현실 세계의 어떤 것도 번역하지 않습니다. 프로세서, 관리자 또는 주지사와 같은 수업이있을 때마다 알람 벨이 울립니다.