c# - برمجة الكائنات-كيفية تجنب الازدواجية في العمليات التي تختلف قليلا اعتمادا على متغير




oop (7)

أنا آسف لأني صغت منذ فترة طويلة مصطلح "كائنات" لهذا الموضوع لأنه يجعل الكثير من الناس يركزون على الفكرة الأقل أهمية. الفكرة الكبيرة هي الرسائل .

~ آلان كاي ، على المراسلة

أود ببساطة تنفيذ إجراءات Capitalise و RemovePunctuation وما إلى ذلك RemovePunctuation فرعية يمكن إرسالها باستخدام معلمات text country وإرجاع نص معالج.

استخدم القواميس لتجميع البلدان التي تتوافق مع سمة معينة (إذا كنت تفضل القوائم ، فستعمل أيضًا مع تكلفة أداء بسيطة). على سبيل المثال: CapitalisationApplicableCountries PunctuationRemovalApplicableCountries .

/// Runs like a pipe: passing the text through several stages of subprocesses
public string Process(string country, string text)
{
    text = Capitalise(country, text);
    text = RemovePunctuation(country, text);
    // And so on and so forth...

    return text;
}

private string Capitalise(string country, string text)
{
    if ( ! CapitalisationApplicableCountries.ContainsKey(country) )
    {
        /* skip */
        return text;
    }

    /* do the capitalisation */
    return capitalisedText;
}

private string RemovePunctuation(string country, string text)
{
    if ( ! PunctuationRemovalApplicableCountries.ContainsKey(country) )
    {
        /* skip */
        return text;
    }

    /* do the punctuation removal */
    return punctuationFreeText;
}

private string Replace(string country, string text)
{
    // Implement it following the pattern demonstrated earlier.
}

الشيء الذي يظهر كثيرًا في عملي الحالي هو أن هناك عملية معممة يجب أن تحدث ، لكن الجزء الغريب من هذه العملية يجب أن يحدث بشكل مختلف قليلاً اعتمادًا على قيمة متغير معين ، وأنا لست كذلك من المؤكد تماما ما هي الطريقة الأكثر أناقة للتعامل مع هذا.

سأستخدم المثال الذي لدينا عادة ، وهو القيام بأشياء بشكل مختلف بعض الشيء حسب البلد الذي نتعامل معه.

لذلك لدي فصل ، دعنا نسميها 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 ، إلخ ... مما يعني أنه يجب عليك إلقاء نظرة على عدة فصول مختلفة لمعرفة جميع الإجراءات المحتملة التي يمكن أن تحدث أثناء التعامل مع علامات الترقيم. من الواضح أنني لا أريد فئة عملاقة واحدة لها مليار if البيانات ، ولكن على نفس القدر من 20 فئة ذات منطق متباين بعض الشيء ، تشعر أيضًا بالملل.

في الأساس ، أعتقد أنني حصلت على نوع من عقدة OOP ولا أعرف طريقة جيدة لفك تشابكها. كنت أتساءل ما إذا كان هناك نمط من شأنه أن يساعد في هذا النوع من العملية؟


كنت أتساءل ما إذا كان هناك نمط من شأنه أن يساعد في هذا النوع من العملية

سلسلة من reponsibility هو نوع من الشيء الذي قد تبحث عنه ولكن في OOP مرهقة إلى حد ما ...

ماذا عن نهج أكثر وظيفية مع C #؟

using System;


namespace Kata {

  class Kata {


    static void Main() {

      var text = "     testing this thing for DEU          ";
      Console.WriteLine(Process.For("DEU")(text));

      text = "     testing this thing for USA          ";
      Console.WriteLine(Process.For("USA")(text));

      Console.ReadKey();
    }

    public static class Process {

      public static Func<string, string> For(string country) {

        Func<string, string> baseFnc = (string text) => text;

        var aggregatedFnc = ApplyToUpper(baseFnc, country);
        aggregatedFnc = ApplyTrim(aggregatedFnc, country);

        return aggregatedFnc;

      }

      private static Func<string, string> ApplyToUpper(Func<string, string> currentFnc, string country) {

        string toUpper(string text) => currentFnc(text).ToUpper();

        Func<string, string> fnc = null;

        switch (country) {
          case "USA":
          case "GBR":
          case "DEU":
            fnc = toUpper;
            break;
          default:
            fnc = currentFnc;
            break;
        }
        return fnc;
      }

      private static Func<string, string> ApplyTrim(Func<string, string> currentFnc, string country) {

        string trim(string text) => currentFnc(text).Trim();

        Func<string, string> fnc = null;

        switch (country) {
          case "DEU":
            fnc = trim;
            break;
          default:
            fnc = currentFnc;
            break;
        }
        return fnc;
      }
    }
  }
}

ملاحظة: ليس من الضروري أن تكون ثابتة تمامًا. إذا كانت حالة فئة العملية بحاجة إلى يمكنك استخدام فئة مدرجة أو وظيفة مطبقة جزئيًا ؛).

يمكنك إنشاء العملية لكل بلد عند بدء التشغيل ، وتخزين كل بلد في مجموعة مفهرسة واستعادتها عند الحاجة بتكلفة O (1).


أود أن أقترح تضمين جميع الخيارات في فصل واحد:

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

وتمريرها إلى طريقة 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);
}

تريد التفويض إلى (إيماءة لسلسلة من المسؤولية) شيء يعرف ثقافته. لذا استخدم أو أنشئ بنية نوع البلد أو CultureInfo ، كما ذكر أعلاه في الإجابات الأخرى.

ولكن بشكل عام وبشكل أساسي مشكلتك هي أنك تبني تصميمات إجرائية مثل "المعالج" وتطبقها على OO. تتعلق OO بتمثيل مفاهيم العالم الحقيقي من مجال العمل أو المشكلة في البرنامج. لا يترجم المعالج إلى أي شيء في العالم الحقيقي بصرف النظر عن البرنامج نفسه. كلما كان لديك فئات مثل المعالج أو مدير أو محافظ ، ينبغي أن أجراس ناقوس الخطر يرن.


ربما يمكن أن يكون لديك 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 أي شيء.


عندما تم تعيين إطار عمل .NET للتعامل مع هذه الأنواع من المشكلات ، لم يصمم كل شيء string . إذن لديك ، على سبيل المثال ، فئة CultureInfo :

يوفر معلومات حول ثقافة معينة (تسمى لغة لتطوير التعليمات البرمجية غير المُدارة). تتضمن المعلومات أسماء الثقافة ونظام الكتابة والتقويم المستخدم وترتيب الفرز بالسلاسل والتنسيق للتواريخ والأرقام.

الآن ، قد لا تحتوي هذه الفئة على الميزات المحددة التي تحتاج إليها ، ولكن من الواضح أنه يمكنك إنشاء شيء مشابه. ثم قمت بتغيير طريقة Process الخاصة بك:

public string Process(CountryInfo country, string text)

بعد ذلك ، يمكن أن تحتوي فئة CountryInfo الخاصة بك على خاصية bool RequiresCapitalization ، وما إلى ذلك ، تساعد طريقة 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."));

إليكم مثال كمان dotnet يعمل ...

يمكنك وضع كل معالجة خاصة بكل بلد في كل فئة من فئات الدول. قم بإنشاء فصل مشترك (في فئة المعالجة) لجميع الأساليب الفردية الفعلية ، بحيث يصبح كل معالج دولة قائمة بالمكالمات الشائعة الأخرى ، بدلاً من نسخ الرمز في كل فئة بلد.

ملاحظة: ستحتاج إلى إضافة ...

using System.Assembly;

من أجل الأسلوب الثابت لإنشاء مثيل لفئة البلد.





oop