Який кращий спосіб викликати метод, доступний лише одному класу, який реалізує інтерфейс, але не іншому?


11

В основному мені потрібно виконувати різні дії за певної умови. Існуючий код пишеться таким чином

Базовий інтерфейс

// DoSomething.java
interface DoSomething {

   void letDoIt(String info);
}

Реалізація першого класу робітників

class DoItThisWay implements DoSomething {
  ...
}

Реалізація другого класу робітників

class DoItThatWay implements DoSomething {
   ...
}

Основний клас

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          worker = new DoItThisWay();
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

Цей код працює нормально і його легко зрозуміти.

Тепер, через нову вимогу, мені потрібно передати новий фрагмент інформації, який має лише сенс DoItThisWay.

Моє запитання: чи корисний наступний стиль кодування для обробки цієї вимоги.

Використовуйте нову змінну класу та метод

// Use new class variable and method

class DoItThisWay implements DoSomething {
  private int quality;
  DoSomething() {
    quality = 0;
  }

  public void setQuality(int quality) {
    this.quality = quality;
  };

 public void letDoIt(String info) {
   if (quality > 50) { // make use of the new information
     ...
   } else {
     ...
   }
 } ;

}

Якщо я це роблю так, мені потрібно внести відповідну зміну абоненту:

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          DoItThisWay tmp = new DoItThisWay();
          tmp.setQuality(quality)
          worker = tmp;

        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

Це гарний стиль кодування? Або я можу це просто кинути

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          worker = new DoItThisWay();
          ((DoItThisWay) worker).setQuality(quality)
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

19
Чому б просто не перейти qualityдо конструктора DoItThisWay?
Девід Арно

Мені, напевно, потрібно переглянути своє питання ... тому що з міркувань продуктивності будівництво DoItThisWayі DoItThatWayробиться один раз у конструкторі Main. Mainє довгим живим класом і doingItйого називають багато разів знову і знову.
Ентоні Конг

Так, оновлення вашого питання було б гарною ідеєю в такому випадку.
Девід Арно

Ви хочете сказати, що setQualityметод буде викликатися кілька разів протягом життя DoItThisWayоб'єкта?
jpmc26

1
Немає відповідної різниці між представленими вами двома версіями. З логічної точки зору, вони роблять те саме.
Себастьян Редл

Відповіді:


6

Я припускаю, що це qualityпотрібно встановити поруч із кожним letDoIt()дзвінком на DoItThisWay().

Я думаю, що тут виникає таке питання: Ви вводите тимчасову зв'язок (тобто, що відбувається, якщо ви забудете зателефонувати setQuality()перед тим, як зателефонувати letDoIt()на DoItThisWay?). А реалізації для DoItThisWayта DoItThatWayрозходяться (один потрібно setQuality()викликати, інший - ні).

Хоча це може не викликати проблем прямо зараз, з часом він може переслідувати вас. Можливо, варто поглянути ще раз letDoIt()і поміркувати, чи може якісна інформація бути частиною того, що infoви проходите через неї; але це залежить від деталей.


15

Це гарний стиль кодування?

З моєї точки зору, жодна з ваших версій не є. Перший виклик, setQualityперш ніж letDoItможна викликати, - це тимчасова зв’язка . Ви застрягли в перегляді DoItThisWayяк на похідній DoSomething, але це не (принаймні, не функціонально), а скоріше щось на зразок а

interface DoSomethingWithQuality {
   void letDoIt(String info, int quality);
}

Це Mainскоріше щось подібне

class Main {
    // omitting the creation
    private DoSomething doSomething;
    private DoSomethingWithQuality doSomethingWithQuality;

    public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
            int quality = obtainQualityInfo();
            doSomethingWithQuality.letDoIt(info, quality);
        } else {
            doSomething.letDoIt(info);
        }
    }
}

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

class Main {
    private DoSomethingFactory doSomethingFactory;

     public doingIt(String info) {
         int quality = obtainQualityInfo();
         DoSomething doSomethingWorker = doSomethingFactory.Create(info, quality);
         doSomethingWorker.letDoIt();
     }
}

Я знаю, що ви це написали

тому що з міркувань продуктивності побудова DoItThisWay і DoItThatWay робиться один раз у конструкторі Main

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


Арх, ти шпигуєш за мною, поки я набираю відповіді? Ми робимо такі подібні моменти (але ваш набагато всебічніший та красномовніший);)
CharonX

1
@DocBrown Так, це дискусійно, але оскільки DoItThisWayнеобхідна якість, яку потрібно встановити перед викликом, letDoItповодиться по-різному. Я б очікував, що A DoSomethingпрацюватиме однаково (не встановлюючи якості раніше) для всіх похідних. Це має сенс? Звичайно, це setQualityщось розмито, адже якби ми зателефонували з фабричного методу, клієнт буде несерйозно ставитися до того, має бути встановлена ​​якість чи ні.
Пол Кертчер

2
@DocBrown Я припускаю, що договір на letDoIt()це (відсутність будь-якої додаткової інформації) "Я можу правильно виконати (робити все, що я роблю), якщо ви надасте мені String info". Вимога, яку setQuality()зателефонують заздалегідь, посилює передумову виклику letDoIt(), і, таким чином, буде порушенням LSP.
CharonX

2
@CharonX: якщо, наприклад, був би контракт, який передбачає, що " letDoIt" не повинен викидати жодних винятків, а забувши викликати "setQuality", це призведе до того, що letDoItвикине виняток, тоді я погоджуюся, така реалізація порушить LSP. Але це для мене схоже на дуже штучні припущення.
Док Браун

1
Це хороша відповідь. Єдине, що я хотів би додати, - це якийсь коментар щодо того, наскільки катастрофічно погана тимчасова зв'язок для кодової бази. Це прихований контракт між, здавалося б, нерозділеними компонентами. При звичайному з'єднанні, принаймні, це явне і легко визначити. Це, по суті, викопує яму і прикриває її листям.
JimmyJames

10

Припустимо, qualityне може бути передано в конструктор, і setQualityвимагається виклик до .

Наразі фрагмент коду, як

    int quality = obtainQualityInfo();
    worker = new DoItThisWay();
    ((DoItThisWay) worker).setQuality(quality);

це занадто мало, щоб вкладати в нього занадто багато думок. ІМХО це виглядає трохи некрасиво, але насправді важко зрозуміти.

Проблеми виникають, коли такий фрагмент коду зростає і завдяки рефакторингу у вас виникає щось подібне

    int quality = obtainQualityInfo();
    worker = CreateAWorkerForThisInfo();
    ((DoItThisWay) worker).setQuality(quality);

Тепер ви не бачите відразу, якщо цей код все-таки правильний, і компілятор не повідомить вам. Таким чином, введення тимчасової змінної правильного типу та уникнення кастингу є трохи більш безпечним для типу, без реальних додаткових зусиль.

Однак я б насправді tmpназвав кращу назву:

      int quality = obtainQualityInfo();
      DoItThisWay workerThisWay = new DoItThisWay();
      workerThisWay.setQuality(quality)
      worker = workerThisWay;

Таке іменування допомагає неправильному коду виглядати неправильним.


tmp може бути ще краще названий чимось на кшталт "workerThatUsesQuality"
user949300

1
@ user949300: IMHO це не дуже гарна ідея: припустимо, DoItThisWayотримує більш конкретні параметри - за тим, що ви пропонуєте, ім’я потрібно змінювати щоразу. Краще використовувати імена для змінних, які дозволяють зрозуміти тип об'єкта, а не імена методів, які доступні.
Док Браун

Для уточнення це було б ім'ям змінної, а не назвою класу. Я погоджуюся, що розміщення всіх можливостей у назві класу - це погана ідея. Тому я пропоную зробити workerThisWayщось подібне usingQuality. У цьому невеликому фрагменті коду безпечніше бути більш конкретним. (І якщо в їхньому домені є краща фраза, ніж "usingQuality", використовуйте її.
user949300

2

Загальний інтерфейс, коли ініціалізація змінюється залежно від даних часу виконання, зазвичай піддається заводському зразку.

DoThingsFactory factory = new DoThingsFactory(thingThatProvidesQuality);
DoSomething doSomething = factory.getDoer(info);

doSomething.letDoIt();

Тоді DoThingsFactory може потурбуватися про отримання та встановлення інформації про якість всередині getDoer(info)методу перед поверненням конкретного об'єкта DoThingsWithQuality, переданого в інтерфейс DoSomething.

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