Слабая связь против инкапсуляции. Лучший подход для сбалансированного дизайна

По следующим примерам:

class InvoiceGenerator
{
function create(Invoice $invoice)
{
$invoice->create();
}
}class InvoiceGenerator
{
function create($invoiceData)
{
$invoice = new Invoice();
$invoice->create($invoiceData);
}
}

Первый пример имеет меньше сцепления между классами InvoiceGenerator и Invoice, поскольку InvoiceGenerator не требует класса Invoice. Кроме того, он может обрабатывать не только класс, но и весь интерфейс с минимальными изменениями. Я много раз читал этот принцип, который гласит: код для интерфейса, а не для реализации. Недостатком этого случая является то, что я вынужден создать экземпляр класса Invoice в клиентском коде.

Второй имеет больше инкапсуляции. Весь процесс создания экземпляра и создания счета-фактуры делегируется классу InvoiceGenerator. Хотя оба класса связаны, это имеет смысл, потому что «генератор счетов» ничего не будет делать без счетов.

Что вы считаете наиболее подходящим? Или каковы ключевые моменты для сбалансированного дизайна между ними обоими?

5

Решение

Прежде всего, я думаю, что вы путаете некоторые вещи. Похоже, ваш класс InvoiceGenerator является заводской класс. В его обязанности входит создание и возврат объекта Invoice. Объект Invoice не является зависимость InvoiceGenerator (в этом случае было бы лучше передать объект Invoice в качестве аргумента, известный как Внедрение зависимости).

Однако, как я уже сказал, класс InvoiceGenerator, по-видимому, является фабричным классом, и вся идея фабричного класса заключается в том, что логика создания объекта инкапсулирована в этом классе (как в вашем втором примере). Если вы передадите объект Invoice в качестве аргумента (как в первом примере), вам придется создать его экземпляр где-то еще, и если в вашем коде есть несколько мест, где это происходит, вы в конечном итоге продублируете логику создания экземпляра. Вся идея фабричного образца состоит в том, чтобы предотвратить это.

Используя фабричный класс, вы можете инкапсулировать логику создания объекта и избежать дублирования. Вы также можете добавить более продвинутую логику в InvoiceGenerator’s create() метод, чтобы решить, какой тип объекта вы хотите вернуть. Например, вы можете вернуть Invoice объект, если общая цена > 0, но CreditNote возражать, если цена < 0, Конечно, они оба должны расширять один и тот же интерфейс (например, InvoiceInterface).

Кроме того, похоже, что вы делегируете фактическую инициализацию объекта Invoice самому объекту, так как create() метод InvoiceGenerator не делает ничего, кроме вызова create() метод объекта Invoice, где кажется, что имеет место действительная логика. Это нарушает принцип единой ответственности, потому что класс Invoice теперь отвечает за хранение данных о счете а также установка всех данных из массива. Последний должен вместо этого быть ответственностью фабричного класса.

Итак, я бы создал класс следующим образом:

class InvoiceFactory
{
public function create(array $data)
{
if ($data['total'] >= 0) {
$invoice = new Invoice();
} else {
$invoice = new CreditNote();
}

$invoice->setTotal($data['total']);
// set other data using setters as well

return $invoice;
}
}

Как видите, нет create() метод вызывается на $invoice объект.

3

Другие решения

Если вы хотите следовать SOLID
, тогда первый вариант наиболее близок к требованиям.

Первый вариант, Класс InvoiceGenerator с одной обязанностью по созданию счета-фактуры и не создает экземпляр объекта счета-фактуры.
Объект счета-фактуры может быть заменен (расширение) подклассом или классом, реализующим интерфейс счета-фактуры.

Второй вариант, объект счета жестко закодирован и не открыт для расширения. Открыта для модификации
если реализация класса счета изменилась в будущем. Кроме того, невозможно (PHPUnit) протестировать InvoiceGenerator с фиктивным экземпляром класса invoice.

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

Возможность сделать класс InvoiceGenerator менее связанным, и вам не нужно создавать экземпляр объекта invoice в клиентском коде, но вы даете возможность клиентскому коду устанавливать свой собственный экземпляр объекта invoice.

class InvoiceGenerator
{
function create(InvoiceInterface $invoice = null)
{
if (null === $invoice) {
$invoice = new Invoice();
}
$invoice->create();
}
}
1

На самом деле это сложная проблема дизайна, если в качестве языка использовать php.

Я предпочитаю меньше связывания, чем инкапсуляции (СУХОЙ, не повторяйте себя)

Для себя я создал программное обеспечение для выставления счетов, как правило, так оно и есть.

Входы:
Часы отработанные (почасовые)
Проданные предметы (предметы)
Переходит в InvoiceGenerator, который выводит счет-фактуру.

class Hourly {
}

class Items {
}

Class InvoiceGenerator {
//Returns Invoice
//A list of hourly objects and items
function createInvoice($state, $hourly = array(), $items = array() {
$invoice =  new Invoice($hourly, $items);
$invoice->applyTaxes($state):

//Do Other Things

return $invoice;
}
}

Ключевыми моментами во всех моих проектах являются № 1 Readabilioty и № 2 Не повторяя себя
Инкапсулируя ваши данные, вы усложняете тестирование и становитесь менее гибкими.

Массив данных может быть разброшен в PHP как объект, но вам не хватает возможности иметь другие функции над данными. Как, например, если 1 объект освобожден от налога. Теперь вы должны добавить к своей массивной структуре массива возможность проверять это.

Или с менее связанным подходом у вас просто есть метод, который возвращает значение налога (0 или иначе).

Я надеюсь, что этот пример показывает, почему менее сплоченный подход часто лучше.

1

Вы должны рассмотреть подход к проблеме с Фабрика Метод Дизайн Шаблон что-то вроде:

class InvoiceFactory
{
static function create($invoiceData)
{
$invoice = new Invoice();
$invoice->create($invoiceData);
return $invoice;
}
}
0