Это пример принципа единой ответственности? - PullRequest
6 голосов
/ 18 марта 2009

Я сделал следующий пример кода, чтобы узнать, как использовать сигнатуру обобщенного метода.

Чтобы получить метод Display () как для клиента, так и для сотрудника, я фактически начал заменять мой интерфейс IPerson на абстрактный класс пользователя .

Но потом я остановился, вспомнив подкаст, в котором дядя Боб рассказывал Скотту Хансельману о Принципе единой ответственности , в котором у вас должно быть много маленьких классов, каждый из которых выполняет одну конкретную вещь, то есть класс Customer не должен иметь Print () и Save () и CalculateSalary () , но у вас должен быть класс CustomerPrinter и Класс CustomerSaver и Класс CustomerSalaryCalculator .

Это кажется странным способом программирования. Однако избавление от моего интерфейса также показалось неправильным (так как многие контейнеры IoC и примеры DI используют их по своей природе), поэтому я решил попробовать Принцип единой ответственности.

Итак, следующий код отличается от того, что я программировал в прошлом (я бы создал абстрактный класс с методом Display () и избавился бы от интерфейса), но основываясь на том, что я слышал о разделении и ТВЕРДОМ принципы, это новый способ кодирования (интерфейс и класс PersonDisplayer) Я думаю, что это правильный путь .

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

using System;

namespace TestGeneric33
{
    class Program
    {
        static void Main(string[] args)
        {
            Container container = new Container();
            Customer customer1 = container.InstantiateType<Customer>("Jim", "Smith");
            Employee employee1 = container.InstantiateType<Employee>("Joe", "Thompson");
            Console.WriteLine(PersonDisplayer.SimpleDisplay(customer1));
            Console.WriteLine(PersonDisplayer.SimpleDisplay(employee1));
            Console.ReadLine();
        }
    }

    public class Container
    {
        public T InstantiateType<T>(string firstName, string lastName) where T : IPerson, new()
        {
            T obj = new T();
            obj.FirstName = firstName;
            obj.LastName = lastName;
            return obj;
        }
    }

    public interface IPerson
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

    public class PersonDisplayer
    {
        private IPerson _person;

        public PersonDisplayer(IPerson person)
        {
            _person = person;
        }

        public string SimpleDisplay()
        {
            return String.Format("{1}, {0}", _person.FirstName, _person.LastName);
        }

        public static string SimpleDisplay(IPerson person)
        {
            PersonDisplayer personDisplayer = new PersonDisplayer(person);
            return personDisplayer.SimpleDisplay();
        }
    }

    public class Customer : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
    }

    public class Employee : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int EmployeeNumber { get; set; }
    }
}

Ответы [ 5 ]

8 голосов
/ 18 марта 2009

Мне нравится думать о Принципе единой ответственности как о реализации разделения обязанностей . Прежде чем я начну разделять свои классы, как вы, я пытаюсь понять, за что должен отвечать каждый класс.

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

Однако, если бы печать и сохранение были более сложными задачами, которые могли бы выполняться различными способами, то выделенный класс Printer или Saver был бы оправдан, поскольку эта ответственность теперь более сложна. Порог «сложности» для создания нового класса очень субъективен и будет зависеть от конкретной ситуации, но, в конце концов, код - это просто абстракция для нас, простых людей, чтобы понять, поэтому сделайте его таким, чтобы он был наиболее интуитивным.

Вы Container класс немного вводит в заблуждение. На самом деле он ничего не «содержит». На самом деле он реализует шаблон метода фабрики и выиграл бы от названия фабрики.

Кроме того, ваш PersonDisplayer никогда не создается и может обеспечить все свои функции с помощью статических методов, так почему бы не сделать его статическим классом? служебные классы , такие как принтеры или заставки, весьма часто бывают статичными. Если у вас нет необходимости иметь отдельные экземпляры принтера с разными свойствами, сохраняйте его статичным.

4 голосов
/ 18 марта 2009

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

Но отделить «быть» клиентом от «показа клиента» приятно. Придерживайтесь этого, это хорошая интерпретация принципов SOLID.

Лично я теперь полностью перестал использовать любые статические методы в этом виде кода, и я полагаюсь на DI, чтобы получить все нужные сервисные объекты в нужном месте и времени. Как только вы начнете более подробно рассказывать о принципах SOLID, вы обнаружите, что проводите гораздо больше занятий. Старайтесь работать над этими соглашениями об именах, чтобы оставаться последовательными.

1 голос
/ 18 марта 2009

Несколько заметок:

  • Вообще говоря, SRP - это все хорошо, равно как и отделение форматирования дисплея от данных.
  • С учетом отображения и т. Д. Я бы скорее подумал с точки зрения сервисов, т. Е. PersonDisplayer является одиночным, не имеет состояния и предлагает функцию отображения строки (IPerson). ИМХО, специальный класс-оболочка просто для отображения не дает никаких преимуществ.
  • Однако, если вы используете привязку данных для wpf, у вас может быть класс DisplayablePerson, который будет распространять PropertyChanged, если Person изменился. Вы должны поместить объекты DisplayablePerson в ObservableCollection и использовать его в качестве ItemsSource некоторого элемента управления списком.
  • Для чего вам нужен контейнер, он предназначен только для создания экземпляра и настройки экземпляра? Попробуйте затем Customer customer1 = new Customer {FirstName = "Jim", LastName = "Smith"};

  • Кстати, я пробовал вызывать object.Method (...) несколько раз, так как это казалось самым быстрым и простым решением. Однако через некоторое время у меня всегда возникали проблемы с этим, и в итоге я получал object.Method (Type someTypeType, ...)

1 голос
/ 18 марта 2009

Ну, я никогда раньше не слышал об этом «едином принципе ответственности», но мне кажется, что то, что вы делаете, имея эти классы CustomerPrinter и CustomerSaver, просто превращает классы обратно в структуры и все ориентирует объект.

Например, это будет означать, что разные типы клиентов будут нуждаться в разных случаях в классе CustomerPrinter, если их нужно печатать по-разному. Но, насколько я понимаю, одна из целей организации OO, а также использования деревьев наследования и всего прочего, состоит в том, чтобы избавиться от необходимости этого CustomerPrinter, чтобы знать, как печатать все: клиенты знают, как печатать сами.

Я ни в коем случае не верю в строгом следовании этим парадигмам. Например, я не уверен, в чем разница между интерфейсом и абстрактным классом в вашем случае. Но опять же, я программист C ++, а не программист C # ...

0 голосов
/ 18 марта 2009

Вы можете взглянуть на IFormattable и IFormatProvider .

Фреймворк имеет классы форматирования для поддержки.

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