Это шаблон дизайна?Если так, то, что это? - PullRequest
2 голосов
/ 29 апреля 2011

Я беру некоторый код для проекта, и я видел кучу скопированного кода в нескольких классах.Владелец очень устал от рефакторинга этого кода, но мне пришла в голову идея, которая звучит для него хорошо.

Учитывая:

Несколько классов "Client", которые НЕимеют точно такой же интерфейс, но довольно близкий:

class Client1
{
    public static string FunctionA(a);
    public static string FunctionB(a, b, c);
    public static string FunctionC(a, b);
}

class Client2
{
    public static string FunctionA(a);
    public static string FunctionB(a, b, c);
    public static string FunctionC(a, b);
}

class Client3
{
    public static string FunctionA(a);
    public static string FunctionC(a, b);
    public static string FunctionD();
}
... etc

Допустим, что FunctionA - это функция, ТОЧНО одинаковая в каждом классе.Владелец почему-то считает, что эта функция должна присутствовать в каждом классе, потому что «она может отличаться для другого клиента в будущем» (fyi: FunctionA преобразует стандартное время в военное время ... поэтому я в этом сильно сомневаюсь).

Каждый из его клиентов имеет специальный код (например, «abc» или «xyz» в файле web.config, поэтому при обращении к коду клиента он получает правильное поведение, используя код, подобный следующему:

public static string FunctionA(string a)
{
  switch(getClientCode())
  {
    case "abc":
      return Client1.FunctionA(a);

    case "xyz":
      return Client2.FunctionA(a);

    case "def":
      return Client3.FunctionA(a);

    default:
      throw new Exception("code not supported");
  }
}

Пусть будет известно, что я ни в коем случае не думаю, что это идеально. Я действительно боролся со своим клиентом (которому принадлежит этот код) в некоторых довольно жарких дискуссиях о принятых им решениях.с этим проектом , поэтому не стреляйте в мессенджера.

Мой клиент считает, что такой способ работы полезен, когда, скажем, я хочу реализовать нового клиента, я могу просто запуститьприложения и пройти через некоторые шаги, пока я не найду и не "исправлю" эти сгенерированные исключения. Вот почему владельцу нравится, как код настраивается таким образом. Однако примерно половина функций в каждом CКласс lient одинаков для каждого клиента ИЛИ одинаков для примерно 80% клиентов.

Я спросил его, почему у него просто нет абстрактного класса, и причина в том, что не все производные классыдолжен или реализовать любую из функций базовых классов.Кроме того, все открытые функции являются статическими (и не имеют переменных-членов), поэтому нет смысла создавать экземпляры объектов.

Мой шаблон:

Использованиеклиентские классы сверху, я хотел бы реализовать что-то вроде этого:

class Client1
{
  // FunctionA is the same for each class
  //public static string FunctionA(a);
  public static string FunctionB(string a, string b, string c);
  // Client1 and Client3 share the same code.
  //public static string FunctionC(a, b); 
}

class Client2
{
  // FunctionA is the same for each class
  //public static string FunctionA(a);
  public static string FunctionB(string a, string b, string c);
  public static string FunctionC(string a, string b);
}

class Client3
{
    // FunctionA is the same for each class
    //public static string FunctionA(a);
    // Client1 and Client3 share the same code.
    //public static string FunctionC(a, b);
    public static string FunctionD();
}

... etc.

class DefaultClient
{
    public static string FunctionA(string a);
    public static string FunctionB(string a, string b, string c);
    public static string FunctionC(string a, string b);
}

class ProxyUtility
{
  private static string getClientCode();

  public static string FunctionA(string a)
  {
    switch (getClientCode())
    {
      case "abc":
      case "def":
      case "xyz":
        return DefaultClient.FunctionA(a);

      default:
        throw new Exception("code not supported");
    }
  }

  public static string FunctionB(string a, string b, string c)
  {
    switch (getClientCode())
    {
      case "abc":
      case "xyz":
        return DefaultClient.FunctionB(a, b, c);

      case "def":
        return string.Empty; // or throw an exception since they don't support this

      default:
        throw new Exception("code not supported");
    }
  }

  public static string FunctionC(string a, string b)
  {
    switch (getClientCode())
    {
      case "abc":
      case "def":
        return DefaultClient.FunctionC(a, b);

      case "xyz":
        return Client2.FunctionC(a, b);

      default:
        throw new Exception("code not supported");
    }
  }

  public static string FunctionD()
  {
    switch (getClientCode())
    {
      case "abc":
      case "xyz":
        return string.Empty; // or throw an exception since they don't support this function.

      case "def":
        return Client3.FunctionD();

      default:
        throw new Exception("code not supported");
    }
  }
}

Вот блок-схема, чтобы понять, как это работает: Some type of Proxy Pattern?

Ответы [ 6 ]

5 голосов
/ 29 апреля 2011

Судя по вашему коду, есть имя для вашего шаблона.

Это называется: Процедурное программирование

3 голосов
/ 29 апреля 2011

Оба способа беспорядок в обслуживании. Кроме того, статический метод вызывает много проблем, поскольку вы не можете их переопределить.

Вместо этого вы должны использовать фабричный шаблон. Сначала настройте свои интерфейсы и клиентов

// put all methods any client will ever need here
public interface IClient 
{
    string FunctionA(string a);
    string FunctionB(string a, string b, string c);
    string FunctionC(string a, string b);
    string FunctionD();
}

// now give a nice default implementation
internal abstract class DefaultClient : IClient
{
    //all clients have the same functionA, for now, don't even allow overriding
    public string FunctionA(string a)
    {
        return "hello: " + a;
    }

    //function B and C differ from client to client make them abstract
    public abstract string FunctionB(string a, string b, string c);
    public abstract string FunctionC(string a, string b);

    //functionD isn't usually needed, but sometimes has an implementation
    public virtual string FunctionD()
    {
         // do nothing I guess
    }
}

internal class Client1 : DefaultClient
{
    // implement functionB and functionC here
}

internal class Client2 : DefaultClient
{
    // implement functionB and functionC here
}

internal class Client3 : DefaultClient
{
    // implement functionB, functionC and functionD here
}

Теперь все, что вам нужно сделать, это создать правильный клиентский класс во время выполнения. Вы собираетесь испытать объектно-ориентированное блаженство.

// here is the factory
public static class ClientFactory
{
    public static IClient GetClient(string clientCode)
    {
      // only 1 place in the code with a switch statement :)
      switch (clientCode)
      {
        case "abc": return new Client1();
        case "def": return new Client2();
        case "xyz": return new Client3();
        default: throw new Exception("code not supported: " clientCode);
      }
  }
}
2 голосов
/ 29 апреля 2011

Предполагая, что у вас нет опечаток и вы решаете все «статические» проблемы, вы пытаетесь сделать «Шаблон дизайна шаблона».Более подробная информация доступна здесь http://www.dofactory.com/Patterns/PatternTemplate.aspx

0 голосов
/ 29 апреля 2011

Читайте по шаблону декоратора.Вот пример главы из самых превосходных шаблонов Head First Design .

http://oreilly.com/catalog/hfdesignpat/chapter/ch03.pdf

Шаблон декоратора - это способ динамического расширения класса с новыми обязанностямиво время выполнения.

0 голосов
/ 29 апреля 2011

Признаюсь, что я только что просмотрел и не прочитал все полностью. ; -)

Существует несколько способов решения этой проблемы, в зависимости от конкретных потребностей. Если вы можете использовать один и тот же интерфейс, у вас есть возможность использовать фабричный шаблон. Предположим, что это, по крайней мере, напрямую. Но вы можете настроить фасад, который создает общий «интерфейс», а затем использовать фабрику (или поставщика и т. Д.).

Если выполняется примерно та же самая работа, но с немного другими методами, вы можете довольно легко стремиться к единице работы или шаблону стратегии.

В общем, вы хотите уйти от операторов switch, если это вообще возможно, так как вы очень вероятно нарушите принцип открытия / закрытия (открыт для расширения, но закрыт для изменения).

0 голосов
/ 29 апреля 2011

Есть отличная книга под названием Head First Design Patterns.В первой главе обсуждается создание интерфейсов для «поведения».У вас может быть один клиентский класс, который может использовать определенное поведение через фабрику клиента.

Также изучите некоторые инфраструктуры IOC (инверсия управления), чтобы вы могли легко внедрить поведения в свои клиентские классы во время выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...