Як можна розкласти конструктор?


21

Скажімо, у мене клас Enemy, і конструктор виглядав би так:

public Enemy(String name, float width, float height, Vector2 position, 
             float speed, int maxHp, int attackDamage, int defense... etc.){}

Це виглядає погано, оскільки у конструктора є стільки параметрів, але коли я створюю екземпляр Enemy, мені потрібно вказати всі ці речі. Я також хочу, щоб ці атрибути були в класі Enemy, щоб я міг повторити їх список і отримати / встановити ці параметри. Я думав, можливо, підкласифікую Enemy в EnemyB, EnemyA, при цьому жорстко кодуючи їх maxHp та інші специфічні атрибути, але тоді я втрачу доступ до їх твердо кодованих атрибутів, якби мені хотілося переробити список Enemy (що складається з EnemyA, EnemyB's та EnemyC's).

Я просто намагаюся навчитися чітко кодувати. Якщо це має значення, я працюю в Java / C ++ / C #. Будь-яка точка в правильному напрямку цінується.


5
Немає нічого поганого в тому, щоб мати один конструктор, який зв’язує всі атрибути. Насправді, в деяких умовах стійкості це потрібно. Ніщо не говорить про те, що ви не можете мати декілька конструкторів, можливо, за допомогою методу перевірки валідності, який потрібно викликати після того, як ви зробите детальну конструкцію.
BobDalgleish

1
Мені доведеться запитати, чи хочете ви коли-небудь побудувати об'єкти Enemy в коді за допомогою літералів. Якщо ви цього не зробите, і я не бачу, чому б ви це зробили, тоді будуйте конструктори, які витягують дані з інтерфейсу бази даних, або рядка серіалізації, або ...
Zan Lynx,


Відповіді:


58

Рішення полягає в поєднанні параметрів у складові типи. Ширина і висота концептуально пов'язані - вони визначають розміри противника і зазвичай знадобляться разом. Вони можуть бути замінені на Dimensionsтип або, можливо, Rectangleтип, який також включає позицію. З іншого боку, це може мати більше сенсу групуватись positionі speedперетворюватися на MovementDataтип, особливо якщо пізніше прискорення потрапить у картину. З контексту я вважаю maxHp, attackDamage, defenseі т.д. , також пов'язані один з одним в Statsтипі. Отже, переглянутий підпис може виглядати приблизно так:

public Enemy(String name, Dimensions dimensions, MovementData movementData, Stats stats)

Точні відомості про те, де намалювати лінії, залежатимуть від решти коду та даних, які зазвичай використовуються разом.


21
Я також хочу додати, що наявність такої кількості значень може вказувати на порушення принципу єдиної відповідальності. А групування значень у конкретні об’єкти - це перший крок у розділенні цих обов'язків.
Ейфорія

2
Я не думаю, що перелік значень є проблемою SRP; більшість з них, ймовірно, призначені для конструкторів базового класу. Кожен клас в ієрархії може нести єдину відповідальність. Enemyце просто клас, на який націлений Player, але їх загальний базовий клас Combatantпотребує статистики боротьби.
MSalters

@MSalters Це не обов'язково вказує на проблему SRP, але це може. Якщо йому потрібно зробити достатню кількість стискань чисел, ці функції можуть знайти свій шлях до класу Enemy, коли вони повинні бути статичними / вільними функціями (якщо він використовує Dimensions/ MovementDataяк звичайні старі контейнери даних) або методами (якщо він перетворює їх на абстрактні дані типи / об’єкти). Наприклад, якщо він ще не створив Vector2тип, він, можливо, закінчив би вести математику Enemy.
Доваль

24

Можливо, ви захочете поглянути на модель Builder . За посиланням (із прикладами шаблону проти альтернатив):

[Шаблон] Builder є хорошим вибором при проектуванні класів, конструктори чи статичні заводи матимуть більше ніж декілька параметрів, особливо якщо більшість цих параметрів є необов'язковими. Клієнтський код набагато простіше читати та писати з будівельниками, ніж з традиційним малюнком конструктора телескопічних, а будівельники набагато безпечніші, ніж JavaBeans.


4
Короткий фрагмент коду буде корисним. Це чудова модель для побудови складних об'єктів чи споруд з різними входами. Ви також можете спеціалізувати будівельників, як EnemyABuilder, EnemyBBuilder тощо, які інкапсулюють різні спільні властивості. Це свого роду зворотний бік фабричного візерунка (як відповіли нижче), але мої особисті переваги - це Builder.
Роб

1
Дякуємо, як модель Builder, так і фабричні моделі виглядають так, що вони добре працюватимуть із тим, що я намагаюся зробити в цілому. Я думаю, що поєднання пропозицій будівельника / фабрики та довалу може бути тим, що я шукаю. Редагувати: я здогадуюсь, що я можу позначити лише одну відповідь; Я дам його Довалу, оскільки він відповідає на питання теми, але інші не менш корисні для моєї конкретної проблеми. Дякую вам всім.
Тревіс

Я думаю, що варто відзначити, що якщо ваша мова підтримує фантомні типи, то ви можете написати шаблон конструктора, який примушує деякі / всі функції SetX викликати. Це також дозволяє гарантувати, що вони також дзвонять лише один раз (за бажанням).
Томас Едінг

1
@ Mark16 Як зазначено у посиланні, > Шаблон Builder імітує іменовані необов'язкові параметри, як це знайдено в Ada та Python. Ви згадали, що ви також використовуєте C # у питанні, і ця мова підтримує назви / необов'язкові аргументи (станом на C # 4.0), так що це може бути інший варіант.
Боб

5

Використання підкласів для встановлення деяких значень небажано. Підклас лише тоді, коли новий тип ворога має іншу поведінку або нові атрибути.

Шаблон заводу зазвичай використовуються , щоб абстрагуватися над точним використовуваним класом, але він також може бути використаний , щоб забезпечити шаблони для створення об'єкта:

class EnemyFactory {

    // each of these methods is essentially a template for a kind of enemy

    Enemy enemyA(String name, ...) {
        return new Enemy(name, ..., presetValue, ...);
    }

    Enemy enemyB(String name, ...) {
        return new Enemy(name, ..., otherValue, ...);
    }

    Enemy enemyC(String name, ...) {
        return new EnemySubclass(name, ..., otherValue, ...);
    }

    ...
}

EnemyFactory factory = new EnemyFactory();
Enemy a = factory.enemyA("fred", ...);
Enemy b = factory.enemyB("willy", ...);

0

Я б зарезервував підкласифікацію для класів, які представляють об'єкт, який ви можете самостійно використовувати, наприклад класу символів, де всі символи, а не лише вороги мають ім'я, швидкість, maxHp або клас для представлення спрайтів, які мають присутність на екрані з шириною, висота, положення.

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

Залежно від мови, яку ви бажаєте використовувати, деякі можуть встановлювати значення за замовчуванням для вхідних параметрів вашого конструктора, наприклад:

Enemy(float height = 42, float width = 42);

0

Приклад коду, який слід додати до відповіді Рорі Хантера (на Java):

public class Enemy{
   private String name;
   private float width;
   ...

   public static class Builder{
       private Enemy instance;

       public Builder(){
           this.instance = new Enemy();
       }


       public Builder withName(String name){
           instance.name = name;
           return this;
       }

       ...

       public Enemy build(){
           return instance;
       }
   }
}

Тепер ви можете створити нові екземпляри Enemy так:

Enemy myEnemy = new Enemy.Builder().withName("John").withX(x).build();

1
Програмісти є концептуальні питання туру, і очікуються відповіді, щоб пояснити речі . Викидання скидів коду замість пояснення - це як копіювання коду з IDE на дошку: це може здатися звичним і навіть іноді бути зрозумілим, але це виглядає дивно ... просто дивно. У дошці немає компілятора
gnat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.