Внедрение конструктора с двумя разными интерфейсами (Single Responsibility и Interface Segregation)

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

class Person
{
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public string Name { get; set; }
    public int Age { get; set; }
}

class DisplayPerson
{
    private readonly IWeapon iwep;
    private readonly Person pers;
    public DisplayPerson(IWeapon iwep, Person perss)
    {
        this.iwep = iwep;
        this.pers = perss;
    }

    public void DisplayAll()
    {
        Console.WriteLine("Name: " + pers.Name + "\nAge: " + pers.Age + "\n" + "Weapon: "
            + iwep.weaponName() + "\nDamage: " + iwep.damage().ToString());
    }
}

Я создал еще один класс для отображения информации, так как правило «S» в SOLID говорит, что класс не должен делать то, что он не должен делать. И я думаю, что отображение этой информации не является его задачей. (Пожалуйста, поправьте меня, если я ошибаюсь)

class Sword : IWeapon
{
    private string weaponNames;
    private int damages;
    public Sword(string weaponName, int damage)
    {
        this.weaponNames = weaponName;
        this.damages = damage;
    }

    public string weaponName(){ return this.weaponNames; }

    public int damage(){ return this.damages; }
}

public interface IWeapon
{
    string weaponName();
    int damage();
}

С помощью этого интерфейса IWeapon я теперь могу добавлять множество видов оружия, у которых есть имя оружия и урон. Но если я хочу добавить еще одно оружие с дополнительными функциями, мы должны следовать принципу ISP, верно? Поэтому я создал этот интерфейс и класс ниже.

class Gun : IWeaponV1
{
    private string weaponNames;
    private int damages;
    private int range;

    public Gun(string weaponName, int damage, int range)
    {
        this.weaponNames = weaponName;
        this.damages = damage;
        this.range = range;
    }

    public string weaponName() { return this.weaponNames; }
    public int damage(){ return this.damages; }
    public int ranges() { return this.range; }
}

public interface IWeaponv1 : IWeapon
{
    int range();
}

И я реализовал это так..

    static void Main(string[] args)
    {
        DisplayPerson disp = new DisplayPerson(new Sword("Samurai Sword", 100), new Person("Jheremy", 19));
        disp.DisplayAll();
        Console.ReadKey();
    }

Моя проблема в том, как мне внедрить IWeaponv1 в класс DisplayPerson выше? Возможно ли это, или мое понимание принципов SOLID просто неверно. Пожалуйста, поправьте меня, если вы видите, что я сделал что-то не так или неправильно :)


comment
Для этой структуры лучше использовать наследование, а не интерфейсы. Например. public abstract class Weapon {} вместо IWeapon.   -  person Chris Pickford    schedule 12.01.2017
comment
Я думаю, что классы в вашем коде являются новыми, а не инъекционными. Дополнительные сведения см. в этой статье: misko. hevery.com/2008/09/30/to-new-or-not-to-new   -  person Yacoub Massad    schedule 12.01.2017
comment
Может быть, вы хотите преобразовать DisplayPerson в сервис под названием PersonDisplayer, который принимает оружие и человека (newables) как параметры метода, а не как зависимости. Взгляните на эту статью: cuttingedge.it/blogs/steven /pivot/entry.php?id=99   -  person Yacoub Massad    schedule 12.01.2017
comment
актуально   -  person pinkfloydx33    schedule 12.01.2017


Ответы (2)


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

  1. Хотя принцип единой ответственности способствует созданию более сплоченных классов, его не следует расширять до уровня, при котором каждая функциональность перемещается в новый класс. Речь идет о классе DisplayPerson, который вообще не требуется (или должен быть реализован по-другому, о чем я вскоре расскажу). Классы представляют объекты реального мира. В реальном мире, если вы спросите человека, как его зовут, он назовет вам свое имя, а не перенаправит вас к другому человеку, который назовет вам свое имя. Таким образом, объекту Person разрешено иметь метод для печати своих собственных свойств, и это никоим образом не противоречит принципу единственной ответственности. То же самое касается и оружия.

  2. Учтите, что вы хотите напечатать range оружия, если оружие поддерживает эту функциональность/поведение. В настоящее время DisplayPerson имеет ссылку на IWeapon, но IWeapon не имеет ranges в рамках контракта. Как тогда вы печатаете range оружия? (range не будет доступно через ссылку iwep в методе DisplayAll). Вам придется использовать typeOfcheck, а затем преобразовать IWeapon в IWeaponV1, чтобы можно было вызвать метод ranges. Проблема с этим подходом заключается в том, что DisplayPerson теперь придется работать с двумя разными интерфейсами, а не с одним интерфейсом. (Не говоря уже об условных проверках, которые противоречат цели использования общего интерфейса)

Помня о вышеуказанных моментах, вы можете внести следующие изменения в код:

  1. Добавьте метод display в интерфейс IWeapon и класс Person.
  2. Вывести свойства человека в методе display класса Person. Точно так же выведите свойства оружия в методе display реализации отдельного оружия.

Благодаря приведенным выше изменениям вы можете вызывать display непосредственно для ваших объектов Person или IWeapon в классе DisplayPerson, полагаясь на единый интерфейс (без нарушения принципа единой ответственности):

public void DisplayAll() {
        Console.WriteLine("Person " + pers.display() + "\n has weapon: " + iwep.display());
}
person CKing    schedule 12.01.2017
comment
Здравствуйте @CKing, вы говорите, что класс DisplayPerson бесполезен? Должен ли я удалить его? - person Jhe; 14.01.2017
comment
Наконец, является ли использование интерфейса для такого рода реализации плохой идеей, и я должен был использовать простое наследование (абстрактный класс), поскольку это новые возможности, которые нельзя вводить? Извините за слишком много вопросов. У вас разные аргументы, и я хотел бы знать, каковы ваши, поскольку я ценю ваш ответ. Заранее спасибо. - person Jhe; 14.01.2017
comment
@Jhe DisplayPerson все в порядке, если вы внесете изменения, которые я предложил. На практике класс можно было бы назвать PersonView, а не DisplayPerson. В реальном мире PersonView будет отвечать за вызов соответствующей библиотеки графического интерфейса для рисования человека вместе с его оружием. Новый не имеет большого значения. Этого можно добиться, используя интерфейсы, удалив конструктор DisplayPerson и вместо этого передав Person и IWeapon методу DisplayAll. Я не понимаю, почему для этого необходимы абстрактные классы. Принятый ответ неверен, как объяснено в моем ответе. - person CKing; 14.01.2017

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

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

  1. Название оружия, урон и дальность — это данные, а не функциональность. Они должны быть объявлены как свойство, а не как метод.
  2. Пистолет должен реализовывать IWeaponv1. Который я думаю, что вы пропустили это по ошибке.
  3. Все свойства задаются в конструкторе, т.е. инъекция конструктора. В этом случае вам не нужен частный набор для класса Person или любых других классов.

Наконец, на ваш актуальный вопрос

DisplayPerson disp = new DisplayPerson(new Sword("Samurai Sword", 100), new Person("Jheremy", 19));

DisplayPerson disp1 = new DisplayPerson(new Gun("Shotgun Axe", 100,100), new Person("Matt", 22);
//Assuming you have implemented Gun:IWeaponv1 

Это выглядит нормально, это не нарушает принцип единой ответственности. Вы слишком много думаете об этом в этом простом примере. В приведенном выше примере существует только один класс (отображаемый человек) с функциональностью, а остальные просто хранят данные. Это хорошо, когда вы отделяете данные от функциональности. Простой способ проверить, нарушена ли ответственность Single, — сверить имя метода с именем класса. Если это неуместно, то вы нарушаете SRP.

person Carbine    schedule 12.01.2017
comment
Я очень ценю ваш ответ. Спасибо за исправление моих ошибок :) Но я думаю, что ваш пример выше DisplayPerson disp1... не будет работать, поскольку вы внедряете класс Gun в IWeapon. Это пока моя большая проблема. - person Jhe; 12.01.2017
comment
Когда вы сделаете Gun:IWeaponv1 , ваша проблема исчезнет. В чем проблема это сделать? - person Carbine; 12.01.2017
comment
МОЙ БОГ! Вы гениальные мужчины. Извините, я исправил свою ошибку в StackOverflow, я думал, что изменил свой код и в своем приложении. Снова простите. - person Jhe; 12.01.2017
comment
@CarbineCoder Я не согласен. Код нарушает принцип единственной ответственности, поскольку он злоупотребляет и неправильно использует этот принцип. Смотрите мой ответ для подробного объяснения. - person CKing; 12.01.2017