Учитывая список различающихся по типу объектов с одним и тем же базовым классом, как может совершенно отдельный класс манипулировать каждым без логики if / else? - PullRequest
0 голосов
/ 14 мая 2018

Контекст: Следующий пример псевдокода демонстрирует шаблон [anti], который присутствует в моем проекте.Этот пример кода на C # был быстро набран, поэтому простите за любые ошибки.

Задача: Я стараюсь избегать как можно большего количества ветвлений (если / иначе, переключатель / регистр) в преследованиииспользования хороших методов ООП, таких как полиморфизм и одиночная ответственность.

Настройка примеров классов:

class Person {
    public string Name;
}

class Employee : Person {
    public int EmployeeID;
}

class Customer : Person {
    public int CustomerID;
}

class PersonDao {
    public void Save(Person person){
    // .. save Person to database
    }
}

class EmployeeDao : PersonDao {
    public override void Save(Person employee){
    // .. save Employee to database
    }
}

class CustomerDao : PersonDao {
    public override void Save(Person customer){
    // .. save Customer to database
    }
}

Проблема:

Person person = new Person{ Name="Frank" };
Person employee =  new Employee{ Name="Alice", EmployeeID=5678};
Person customer = new Customer{ Name="Nellie", CustomerID=123};

PersonDao personDao = new PersonDao();
EmployeeDao employeeDao = new EmployeeDao();
CustomerDao customerDao = new CustomerDao();

List<Person> people = new List<Person>();
people.Add(person);
people.Add(employee);
people.Add(customer);

foreach (Person person in people){
    if (person.GetType() == typeof(Person)) 
        personDao.Save(person);
    else if (person.GetType() == typeof(Customer)) 
        customerDao.Save(person);
    else if (person.GetType() == typeof(Employee)) 
        employeeDao.Save(person);
}

Вопрос в деталях: Просмотр содержимого цикла foreach с запросом каждого объекта о том, какой это тип, является полной противоположностью идее полиморфизма.Моя первая реакция состоит в том, чтобы предложить каждому классу Person возможность Save ().Однако это казалось бы слишком большой ответственностью для класса.

Полагаю, мог бы существовать и некоторый промежуточный класс, который скрывает это ветвление в другом месте, но мне трудно понять, как это сделать без каких-либо операций if / else.

Чтошаблон дизайна или архитектурный сбой, я здесь скучаю?Как бы вы запрограммировали это, учитывая, что подклассы Person должны быть сохранены или иным образом обработаны отдельным классом?

Ответы [ 3 ]

0 голосов
/ 14 мая 2018

Не чистый твердый ответ, а идея:

Вы можете объявить новый интерфейс для ваших объектов DAO:

interface PersonSaver
{
    void Save(Person person);
}

class PersonDao : PersonSaver {
    public void Save(Person person){
    // .. save Person to database
    }
}

class EmployeeDao : PersonSaver {
    public void Save(Person employee){
    // .. save Employee to database
    }
}

class CustomerDao : PersonSaver {
    public void Save(Person customer){
    // .. save Customer to database
    }
}

Затем вы можете включить PersonSaver, который должен использовать каждый класс:

class Person {
    public string Name;
    public virtual PersonSaver Saver
    {
        get
        {
            return new PersonDao();
        }
    }
}

class Employee : Person {
    public int EmployeeID;
    public override PersonSaver Saver
    {
        get
        {
            return new EmployeeDao();
        }
    }
}

class Customer : Person {
    public int CustomerID;
    public override PersonSaver Saver
    {
        get
        {
            return new CustomerDao();
        }
    }
}

Наконец, это будет ваш блок foreach:

foreach (Person person in people){
    person.Saver.Save();
}
0 голосов
/ 14 мая 2018

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

Естественный ООП способ сделать это - добавить виртуальный метод Save в класс Person и переопределить его во всех потомках.Но мы, очевидно, будем нарушать несколько принципов проектирования, делая это.

По этой причине существует шаблон Visitor , который позволяет добавлять пользовательские операции в существующую структуру данных.

Это может быть реализовано таким образом для вашего сценария.

public interface IPersonVisitor
{
    void Visit(Person person);
    void Visit(Employee employee);
    void Visit(Customer customer);

    /*Visit method overload for every item in hierarchy*/
} 

Метод, принимающий посетителя в базовом классе иерархии

public class Person
{
    public string Name;

    public virtual void Accept(IPersonVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Переопределяется во всех потомках

public class Employee : Person
{
    public int EmployeeID;

    public override void Accept(IPersonVisitor visitor)
    {
        visitor.Visit(this);
    }
}

И фактическая реализация операции для каждого типа

public class DaoPersonVisitor: IPersonVisitor
{
    public void Visit(Person person)
    {
        var personDao = new PersonDao();
        personDao.Save(person);
    }

    public void Visit(Employee employee)
    {
        var employeeDao = new EmployeeDao();
        employeeDao.Save(employee);
    }

    public void Visit(Customer customer)
    {
        var customerDao = new CustomerDao();
        customerDao.Save(customer);
    }
}

Тогда вы могли бы назвать это следующим образом

var person = new Employee();
person.Accept(new DaoPersonVisitor());

Разрешение перегрузки сыграет свою роль и DaoPersonVisitor.Visit(Emploee) будет вызван.

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

Вероятно, будет достаточно переместить логику выбора DAO в одно место, поскольку это удалит все дубликаты.

Вам потребуется интерфейс или базовый класс для вашегоТипы DAO, реализующие его во всех потомках.

public interface IPersonDao
{
    void Save(Person person);
}

И фабричный класс (или просто метод, если эта логика может быть помещена в некоторые границы классов)который возвращает соответствующий DAO

public static class PersonDaoFactory
{
    public static IPersonDao Create(Person person)
    {
        if (person is Employee)
        {
            return new EmployeeDao();
        }

        if (person is Customer)
        {
            return new CustomerDao();
        }

        /* cases for all the other object in hierarchy */

        throw new NotSupportedException($"Can't create DAO for '{person.GetType().FullName}'. Type is not supported.");
    }
}

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

0 голосов
/ 14 мая 2018

Что, если вы сделаете его общим, как показано ниже

class EntityDao<T> : PersonDao {
    public override void Save(T employee){
    // .. save Employee to database
    }
}

Тогда вы, вероятно, можете сделать как

EntityDao<Employee> dao = new EntityDao<Employee>();
dao.Save(emp);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...