Сегодня мы рассмотрим паттерн абстрактная фабрика, или фабрика, как его часто называют. Этот паттерн относится к группе порождающих и решает проблему создания группы объектов. Классический пример использования фабрики для предоставления приложению элементов интерфейса в зависимости от платформы. Например, приложение на linux будет создавать при помощи фабрики кнопки, поля и прочие элементы через фабрику, которая в свою очередь, по запросу будет возвращать элементы XWindows, а приложение на windows будет возвращать элементы win32. Таким образом программе становится не важна платформа, платформо-зависимая логика ляжет на плечи фабрики.
Паттерн абстрактная фабрика должен использоваться тогда когда:
- Конкретный вариант требуемого поведения системы дают не отдельные объекты, а четко выраженное семейство связанных объектов
- Объекты одного семейства должны использоваться вместе
- Система должна кофигурироваться одним из семейств объектов
Вот как это выглядит на диаграмме:
В моем примере фабрики мы имеем три вида игровых юнитов Protos, Terran, Zerg (должно быть близко любому кто играл в StarCraft), и некоторое количество игровых юнитов (ботов). Боты создают солдатов нужного типа в зависимости от количества ресурсов добытых работягами, а также боты могут сражаться друг с другом теми юнитами, что были созданы.
Вот код примера, несколько многословный, в силу специфичности синтаксиса явы:
// IUnit.java
package patterns.factory.unit;
public interface IUnit {
public int getGroundWeapon();
public int getAirWeapon();
public int getShield();
public int getPrice();
public String toString();
}
// CustomUnit.java
package patterns.factory.unit;
public class CustomUnit implements IUnit {
@Override
public int getAirWeapon() {
return 0;
}
@Override
public int getGroundWeapon() {
return 0;
}
@Override
public int getPrice() {
return 0;
}
@Override
public int getShield() {
return 0;
}
public String toString() {
return "a:" + getAirWeapon() + " g:" + getGroundWeapon() + " s:" + getShield() + " p:" + getPrice();
}
}
// ProtosSoldier.java
package patterns.factory.unit;
public class ProtosSoldier extends CustomUnit implements IUnit {
@Override
public int getAirWeapon() {
return 10;
}
@Override
public int getGroundWeapon() {
return 12;
}
@Override
public int getPrice() {
return 100;
}
@Override
public int getShield() {
return 10;
}
}
// Terransoldier.java
package patterns.factory.unit;
public class TerranSoldier extends CustomUnit implements IUnit {
@Override
public int getPrice() {
return 35;
}
@Override
public int getAirWeapon() {
return 5;
}
@Override
public int getGroundWeapon() {
return 8;
}
@Override
public int getShield() {
return 3;
}
}
// ZergSoldier.java
package patterns.factory.unit;
public class ZergSoldier extends CustomUnit implements IUnit {
@Override
public int getAirWeapon() {
return 0;
}
@Override
public int getGroundWeapon() {
return 6;
}
@Override
public int getPrice() {
return 25;
}
@Override
public int getShield() {
return 5;
}
}
// IArmyFactory.java
package patterns.factory.army;
import patterns.factory.unit.IUnit;
public interface IArmyFactory {
public IUnit createSoldier();
}
// ProtosArmy.java
package patterns.factory.army;
import patterns.factory.unit.IUnit;
import patterns.factory.unit.ProtosSoldier;
public class ProtosArmy implements IArmyFactory {
@Override
public IUnit createSoldier() {
return new ProtosSoldier();
}
}
// TerranArmy.java
package patterns.factory.army;
import patterns.factory.unit.IUnit;
import patterns.factory.unit.TerranSoldier;
public class TerranArmy implements IArmyFactory {
@Override
public IUnit createSoldier() {
return new TerranSoldier();
}
}
// ZergArmy.java
package patterns.factory.army;
import patterns.factory.unit.IUnit;
import patterns.factory.unit.ZergSoldier;
public class ZergArmy implements IArmyFactory {
@Override
public IUnit createSoldier() {
return new ZergSoldier();
}
}
// BotClient.java
package patterns.factory.client;
import java.util.ArrayList;
import java.util.Iterator;
import patterns.factory.army.IArmyFactory;
import patterns.factory.unit.IUnit;
public class BotClient {
private ArrayList <IUnit> units;
private IArmyFactory factory;
public BotClient(IArmyFactory army) {
factory = army;
units = new ArrayList <IUnit> ();
}
public void createForMoney(int money) {
Boolean hasMoney = false;
units.clear();
do {
IUnit unit = factory.createSoldier();
hasMoney = money - unit.getPrice() > 0;
if(hasMoney){
units.add(unit);
money -= unit.getPrice();
}
} while(hasMoney);
}
public int getGroundWeapon() {
int total = 0;
Iterator iter = units.iterator();
while (iter.hasNext()) {
total += ((IUnit)iter.next()).getGroundWeapon();
}
return total;
}
public int getShield() {
int total = 0;
Iterator iter = units.iterator();
while (iter.hasNext()) {
total += ((IUnit)iter.next()).getShield();
}
return total;
}
public int unitsCount() {
return units.size();
}
public Boolean battle(BotClient client) {
System.out.println("Units: " + unitsCount() + " vs " + client.unitsCount());
System.out.println("Attack: " + getGroundWeapon() + " vs " + client.getGroundWeapon());
System.out.println("Shield: " + getShield() + " vs " + client.getShield());
float damage = (float)client.getGroundWeapon() / (float)getShield();
System.out.println("Damage: " + damage);
float enemyDamage = (float)getGroundWeapon() / (float)client.getShield();
System.out.println("Enemy damage: " + enemyDamage);
return enemyDamage < damage;
}
}
// TestApp.java
package patterns.factory;
import patterns.factory.army.ProtosArmy;
import patterns.factory.army.TerranArmy;
import patterns.factory.army.ZergArmy;
import patterns.factory.client.BotClient;
public class TestApp {
public static void main(String[] args) {
BotClient bot1 = new BotClient(new ProtosArmy());
BotClient bot2 = new BotClient(new TerranArmy());
BotClient bot3 = new BotClient(new ZergArmy());
bot1.createForMoney(400);
bot2.createForMoney(400);
bot3.createForMoney(400);
System.out.println(bot1.battle(bot2));
System.out.println(bot1.battle(bot3));
}
}
Результат работы примера:
Units: 3 vs 11 Attack: 36 vs 88 Shield: 30 vs 33 Damage: 2.9333334 Enemy damage: 1.0909091 false Units: 3 vs 15 Attack: 36 vs 90 Shield: 30 vs 75 Damage: 3.0 Enemy damage: 0.48 false