Давайте розглянемо це практично
Obj 3
тепер знає, що Obj 4
існує. І що? Чому нас хвилює?
DIP каже
"Модулі високого рівня не повинні залежати від модулів низького рівня. Обидва повинні залежати від абстракцій."
Гаразд, але чи не всі об'єкти абстракції?
Також каже DIP
"Абстракції не повинні залежати від деталей. Деталі повинні залежати від абстракцій."
Гаразд, але якщо мій об’єкт правильно інкапсульований, чи не приховує жодної деталі?
Дехто любить сліпо наполягати, що кожен об’єкт потребує інтерфейсу ключових слів. Я не з них. Я люблю сліпо наполягати на тому, що якщо ви зараз не збираєтесь їх використовувати, то вам потрібен план, щоб розібратися з необхідністю чогось подібного пізніше.
Якщо ваш код має можливість повністю рефактор на кожному релізі, ви можете просто витягти інтерфейси пізніше, якщо вони вам потрібні. Якщо ви опублікували код, який ви не хочете перекомпілювати, і ви хочете, щоб ви розмовляли через інтерфейс, вам знадобиться план.
Obj 3
знає, що Obj 4
існує. Але чи Obj 3
знає, чи Obj 4
конкретний?
Це саме тут, чому так приємно НЕ поширюватися new
скрізь. Якщо Obj 3
не знає, чи Obj 4
конкретний він, швидше за все, тому, що він не створив його, то, якщо ви приїхали пізніше і перетворилися Obj 4
на абстрактний клас, Obj 3
це не хвилює.
Якщо ви можете це зробити, то Obj 4
був весь час абстрактним. Єдине, що робить інтерфейс між ними з самого початку, - це впевненість у тому, що хтось не випадково додасть код, який видає Obj 4
конкретний зараз. Захищені конструктори можуть зменшити цей ризик, але це призводить до іншого питання:
Чи Obj 3 і Obj 4 в одному пакеті?
Об'єкти часто так чи інакше групуються (пакет, простір імен тощо). При групі розумно змінюйте більше ймовірних впливів у групі, а не в межах груп.
Мені подобається групуватися за ознаками. Якщо Obj 3
і Obj 4
знаходяться в тій же групі і шар це дуже малоймовірно , що ви опублікували один і не хочете , щоб реорганізувати його в той час як потрібно змінювати тільки інший. Це означає, що ці об’єкти мають менше шансів отримати абстракцію між ними, перш ніж вона матиме явну потребу.
Якщо ви перетинаєте групову межу, хоча це дійсно добра ідея, щоб об'єкти з обох сторін змінювалися незалежно.
Це повинно бути таким простим, але, на жаль, і Java, і C # зробили невдалий вибір, який ускладнює це.
У C # традиція називати кожен інтерфейс ключового слова з I
префіксом. Це змушує клієнтів пізнавати, що вони спілкуються з інтерфейсом ключових слів. Це суперечить плану рефакторингу.
У Java традиційно використовувати кращу схему імен: FooImple implements Foo
Однак це допомагає лише на рівні вихідного коду, оскільки Java компілює інтерфейси ключових слів у інший бінарний. Це означає, що коли ви перейменовуєте Foo
з конкретних на абстрактних клієнтів, яким не потрібен жоден символ зміненого коду, все одно потрібно перекомпілювати.
Саме БУГИ на цих конкретних мовах не дозволяють людям відкладати формальне абстрагування, поки їм це справді не потрібно. Ви не сказали, якою мовою користуєтесь, але зрозуміли, що є деякі мови, у яких просто немає цих проблем.
Ви не сказали, якою мовою ви користуєтесь, тому я просто закликаю вас уважно проаналізувати свою мову та ситуацію, перш ніж ви вирішите, що це буде інтерфейс ключових слів скрізь.
Принцип YAGNI тут відіграє ключову роль. Але так і "Будь ласка, важко застрелити себе в ногу".