В чем разница между 'фабричным методом' и 'простой фабрикой'?

Здравствуйте, хотелось бы узнать в чем разница между этими двумя паттернами. Судя по примерам кода, написанным по примерам с сайта по java:
<?php
abstract class IMobile{}
class Nokia extends IMobile{}
class IPhone extends IMobile{}

Простая фабрика:
<?php
class MobileFactory{
   public function getMobile( $type ){
      if( $type === 'nokia' ){
         return new Nokia();
      } else if( $type === 'iphone' ) {
         return new IPhone();
      }
   }
}

Фабричный метод:
<?php
abstract class IMobileFactory{
   abstract public function getMobile();
}
class NokiaFactory extends IMobileFactory{
   public function getMobile(){
      return new Nokia();
   }
}
class IPhoneFactory extends IMobileFactory{
   public function getMobile(){
      return new IPhone();
   }
}

Получается что разница между этими двумя паттернами только в их вызове?
<?php
(new MobileFactory())->getMobile('nokia');
(new NokiaFactory())->getMobile();

Не может же существовать два различных паттерна, а единственное их отличие это просто вызов
  • Вопрос задан
  • 5573 просмотра
Пригласить эксперта
Ответы на вопрос 3
Adamos
@Adamos
В вашем же примере единственное назначение класса фабрики - создание подклассов в одном методе.
А классы с фабричным методом умеют свой функционал ПЛЮС могут вернуть себя единообразным для всех них образом. Причем этот самый метод getMobile может заодно участвовать и в других методах того же класса, когда ему понадобилось создать клона, например.

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

Паттерны - это же не о классах, а о взаимодействии между ними и уменьшении связности.
Ответ написан
Комментировать
@rundle
Привет.

Пример с сайта неудачный. Он не показывает выразительность фабричного метода в отличие от фабрики. Я изменил пример ниже.

Простая фабрика не является паттерном проектирования, это скорее идиома программирования. Простая фабрика предоставляет интерфейс для создания одного продукта. Отличие фабричного метода в создании набора классов, создающих экземпляры - конкретные классы расширяют класс с фабричным методом.

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

Например, мы открыли в Москве и Питере магазин по продаже телефонов и в классе BeelineStore есть общие методы для всех телефонов (код на java схематичный). Samsung стал поддерживать 5G интернет, поэтому сделаем тариф для этих телефонов дороже.

spoiler
abstract class IMobile {
	void prepare() {
		System.out.println("Подготовим документы по продаже от ООО Билайн");
	}
	void setInternetTariff() {
		System.out.println("Установим стандартный тариф Билайна для интернета");
	}
	void box() {
		System.out.println("Упакуем в желтый пакетик");
	}
}

class BeelineSamsung extends IMobile {
	void setInternetTariff() {
		System.out.println("Установим отдельный тариф для 5G интернета");
	}
}

class BeelineIPhone extends IMobile {}

class BeelineStore {
	public IMobile orderPhone(String type) {
		IMobile phone = null;

		if (type.equals("samsung")) {
			phone = new BeelineSamsung();
		} else if (type.equals("iphone")) {
			phone = new BeelineIPhone();
		}

		phone.prepare();
		phone.setInternetTariff(); 
		phone.box();

		return phone;
	}
}

// использование
// var mobileStore = new BeelineStore();
// mobileStore.orderPhone("samsung"); <- клиентский код

Однако при изменении, удалении или добавлении новых видов телефонов мы бы открывали наш класс BeelineStore и расширяли новыми конкретными типами. А если мы добавим модели телефонов, то дерево условий станет сложнее. Если одни аспекты системы изменяются, а другие остаются неизменными — пора заняться инкапсуляцией.

spoiler
class MobileFactory {
	public IMobile getMobile(String type) {
		if (type.equals("samsung")) {
			return new BeelineSamsung();
		} else if (type.equals("iphone")) {
			return new BeelineIPhone();
		}

		return null;
	}
}

class BeelineStore {
	MobileFactory factory;

	public BeelineStore(MobileFactory factory) {
		this.factory = factory;
	}

	public IMobile orderPhone(String type) {
		IMobile phone;

		phone = factory.getMobile(type);

		phone.prepare(); 
		phone.setInternetTariff();
		phone.box();

		return phone;
	}
}
// использование 
// var mobileFactory = new MobileFactory();
// mobileStore = new BeelineStore(mobileFactory);
// mobileStore.orderPhone("samsung"); <- замечу, клиентский код не изменился

Окей - мы вынесли процесс создания телефонов - это и есть фабрика. И тут мы расширяемся, дела в нашей компании пошли хорошо и мы открываем филиалы на Урале. Тарифы на Урале будут дешевле, как стандартные, так и для 5G интернета. А также в Москве и Питере запускается программа по обмену старых телефонов на новые. И тут нам помогает фабричный метод.

spoiler
abstract class BeelineStore { // класс становится абстрактным
	protected abstract IMobile getMobile(String type); // возвращается метод getMobile

	public IMobile orderPhone(String type) {
		IMobile phone = getMobile(type);

		phone.prepare(); 
		phone.setInternetTariff();
		phone.box(); 

		return phone;
	}
}

class CenterBeelineSamsung extends IMobile {
	void setInternetTariff() {
		System.out.println("Отдельный тариф для 5G интернета в центре России");
	}
}

class CenterBeelineIPhone extends IMobile {}

class СenterBeelineStore extends BeelineStore {
	public IMobile getMobile(String type) {
		if (type.equals("samsung")) {
			return new CenterBeelineSamsung();
		} else if (type.equals("iphone")) {
			return new CenterBeelineIPhone();
		}

		return null;
	}
	
	public void tradeIn(String phone) {
		System.out.println("Вы обменяли старый телефон на новый с доплатой");
	}
}

class UralBeelineSamsung extends IMobile {
	void setInternetTariff() {
		System.out.println("Отдельный тариф для 5G интернета на Урале");
	}
}

class UralBeelineIPhone extends IMobile {
	void setInternetTariff() {
		System.out.println("Отдельный тариф для стандартного интернета на Урале");
	}
}

class UralBeelineStore extends BeelineStore {
	public IMobile getMobile(String type) {
		if (type.equals("samsung")) {
			return new UralBeelineSamsung();
		} else if (type.equals("iphone")) {
			return new UralBeelineIPhone();
		}

		return null;
	}
}
// использование
// var centerBeelineStore = new СenterBeelineStore();
// centerBeelineStore.orderPhone("samsung");
// var uralBeelineStore = new UralBeelineStore();
// uralBeelineStore.orderPhone("samsung");

Мы смогли сохранить единые для всех магазинов стандарты оформления заказа, а также добавили региональные отличия.
То есть фабричный метод гораздо гибче. Простая Фабрика обладает узкой специализацией, а фабричный метод ведет к созданию инфраструктуры, в которой реализация выбирается субклассами.
Простая Фабрика инкапсулирует создание объектов, но она лишена гибкости фабричного метода в изменении создаваемых продуктов.
Ответ написан
Комментировать
@xfg
Фабричный метод

Фабричный метод позволяет избавить клиента от необходимости знать о процессе создания конкретных объектов. Клиент зависит только от интерфейса фабричного метода. Клиент используя фабричный метод может создавать и работать с любым объектом реализующим интерфейс IMobile.

Простая фабрика

Строго говоря в GoF нет такого паттерна как "простая фабрика". Но тем не менее это откуда-то пошло и подобный кусок кода стали называть "простой фабрикой", об этом есть упоминание в википедии и что в это можно инкапсулировать сложный процесс создания объекта, например когда он зависит от настроек в конфигурационном файле или от пользовательского ввода.

Я не вижу паттерн в простой фабрике. Я вижу лапшу из операторов ветвления. Вместо этого пользовательский ввод или настройки конфига можно напрямую без свалки из операторов ветвления сконвертировать в имя требуемого класса фабричного метода и уже его объект передавать клиенту таким образом избавившись от спагетти-кода в виде "простой фабрики".

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

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы