Здравствуйте !!!
Недавно, я приступил к изучению паттернов проектирования. Один из самых первых паттернов, который был реализован - это Builder. Какие у Вас есть замечания по данной реализации. Заранее спасибо !
Исходный код:
1) Абстрактный класс BaseCarBuilder
public abstract class BaseCarBuilder {
public abstract void buildCategory(CategoryCar pCategoryCar);
public abstract void buildCarcass(CarcassType pCarcassType) ;
public abstract void buildEngine(Engine pEngine);
public abstract void buildColorCarcass(ColorCar pColorCar) ;
public abstract void buildDoors(int pDoors) ;
public Car getBuiltCar() {
return null;
}
}
2) Класс Car.
public class Car {
public final static int TWO_DOORS = 2;
public final static int THREE_DOORS = 3;
public final static int FOUR_DOORS = 4;
public final static int FIVE_DOORS = 5;
private int doorsCount;
private Engine engine;
private String carName;
private ColorCar colorCar;
private CarcassType carcassType;
private CategoryCar categoryCar;
public Car(String pCarName) {
this.carName = pCarName;
}
public ColorCar getColorCar() {
return colorCar;
}
public void setColorCar(ColorCar colorCar) {
this.colorCar = colorCar;
}
public CarcassType getCarcassType() {
return carcassType;
}
public void setCarcassType(CarcassType carcassType) {
this.carcassType = carcassType;
}
public CategoryCar getCategoryCar() {
return categoryCar;
}
public void setCategoryCar(CategoryCar categoryCar) {
this.categoryCar = categoryCar;
}
public int getDoorsCount() {
return doorsCount;
}
public void setDoorsCount(int doorsCount) {
this.doorsCount = doorsCount;
}
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public String getCarInfo() {
String info =
"car name = " + carName
+ " *** category car = " + categoryCar
+ " *** carcass tape = " + carcassType
+ " *** engine = " + engine
+ " *** color = " + colorCar
+ " *** count doors = " + doorsCount;
System.err.println("---------------------------------------------------");
return info;
}
}
3) Класс MersedesBenzBuilder, который наследуется от BaseCarBuilder
public class MersedesBenzBuilder extends BaseCarBuilder {
private Car car;
public MersedesBenzBuilder() {
car = new Car("Mersedes Benz");
}
@Override
public void buildCategory(CategoryCar pCategoryCar) {
car.setCategoryCar(pCategoryCar);
}
@Override
public void buildCarcass(CarcassType pCarcassType) {
car.setCarcassType(pCarcassType);
}
@Override
public void buildDoors(int pDoors) {
car.setDoorsCount(pDoors);
}
@Override
public void buildEngine(Engine pEngine) {
car.setEngine(pEngine);
}
@Override
public void buildColorCarcass(ColorCar pColorCar) {
car.setColorCar(pColorCar);
}
}
4) Есть класс Директор (Диспетчер).
public class Director {
public Car buildNewCar(BaseCarBuilder carBuilder, CategoryCar pCategoryCar,
CarcassType pCarcassType, int pDoors, Engine pEngine, ColorCar pColorCar) {
Car tempCar = null;
if (carBuilder != null) {
carBuilder.buildCategory(pCategoryCar);
carBuilder.buildCarcass(pCarcassType);
carBuilder.buildDoors(pDoors);
carBuilder.buildEngine(pEngine);
carBuilder.buildColorCarcass(pColorCar);
tempCar = carBuilder.getBuiltCar();
} else {
final String nullPoint = "Yoa are not create car_builder";
Exception exception = new NullPointerException(nullPoint);
exception.printStackTrace();
}
return tempCar;
}
}
5) Класс который запускает этот паттерн RunBuilderPattern.
public class RunBuilderPattern {
public static void main(String[] args) {
Car car = null;
Director director = new Director();
MersedesBenzBuilder mersedesBenzBuilder = new MersedesBenzBuilder();
car = director.buildNewCar(
mersedesBenzBuilder,
CategoryCar.SPORT_CAR,
CarcassType.CABRIOLET,
Car.TWO_DOORS, Engine.PETROL,
ColorCar.RED);
System.out.println("CAR INFO: " + car.getCarInfo());
}
}
6) Дополнительные классы, точнее Enum-ы:
public enum CarcassType {
SEDAN, UNIVERSAL, CABRIOLET, HATCHBACK, COUPE, LIMOUSINE
}
public enum CategoryCar {
CARGO, PASSENGER, BUS, SPORT_CAR
}
public enum ColorCar {
BLACK, WHITE, RED, GREEN, BLUE, ORANGE, BROWN
}
public enum Engine {
DIZEL, PETROL, GAZ, HUBRID
}
7) Вот что выводится в консоль:
CAR INFO: car name = Mersedes Benz *** category car = SPORT_CAR *** carcass tape = CABRIOLET *** engine = PETROL *** color = RED *** count doors = 2
Жду Ваших замечаний, советов, ругани и брани !!!
Ответ
Всегда пожалуйста:
public abstract class BaseCarBuilder {
// Мне почему-то кажется, что для примера можно было обойтись двумя-тремя
// методами вида buildSomething. Ваш текущий код — это один большой
// boilerplate, где из-за геттеров и сеттеров теряется смысл.
public abstract void buildCategory(CategoryCar pCategoryCar);
// Carcass — крайне неудачный термин. См. http://goo.gl/SCMO1q
public abstract void buildCarcass(CarcassType pCarcassType) ;
// Параметры с именами вида pSomething не следуют naming convention.
// Если бы вы, кстати, инвертировали слова в именах вида pColorCar ---->
// carColor (или просто color), уже стало бы лучше и понятнее.
// pCategoryCar в текущем варианте означает что-то в духе "машина с
// какой-то там категорией", а используется в значении "категория машины".
public abstract void buildEngine(Engine pEngine);
// Опять-таки, плывет нейминг. В текущем варианте по названиям методов
// получается, что ваш builder можно попросить сделать "просто carcass", а
// можно "цветной carcass". Почему бы не объединить buildColorCarcass и
// buildCarcass в один метод или просто сделать buildCarcass + buildColor?
public abstract void buildColorCarcass(ColorCar pColorCar) ;
// Эти замечания — не очень принципиальные. См. ниже.
public abstract void buildDoors(int pDoors) ;
public Car getBuiltCar() {
return null;
}
public class Car {
// public final static int TWENTY_ELEVEN_DOORS = 2011. Почему не enum?
public final static int TWO_DOORS = 2;
public final static int THREE_DOORS = 3;
public final static int FOUR_DOORS = 4;
public final static int FIVE_DOORS = 5;
private int doorsCount;
private Engine engine;
private String carName;
private ColorCar colorCar;
private CarcassType carcassType;
private CategoryCar categoryCar;
public Car(String pCarName) {
this.carName = pCarName;
}
public ColorCar getColorCar() {
return colorCar;
}
....
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
// Погодите, а не 'toString' ли это, часом? Вы, кстати, задумались над тем,
// что, если написать в *произвольном* месте кода вот это,
//
// [[[
// String info = car.getCarInfo();
// info = car.getCarInfo();
// info = car.getCarInfo();
// ......
// info = car.getCarInfo();
// ]]]
//
// то ваш stderr окажется зафлужен строчками вида "------------------"?
public String getCarInfo() {
String info =
"car name = " + carName
+ " *** category car = " + categoryCar
+ " *** carcass tape = " + carcassType
+ " *** engine = " + engine
+ " *** color = " + colorCar
+ " *** count doors = " + doorsCount;
System.err.println("---------------------------------------------------");
return info;
}
}
public class MersedesBenzBuilder extends BaseCarBuilder {
private Car car;
public MersedesBenzBuilder() {
car = new Car("Mersedes Benz");
}
@Override
public void buildCategory(CategoryCar pCategoryCar) {
car.setCategoryCar(pCategoryCar);
}
....
@Override
public void buildColorCarcass(ColorCar pColorCar) {
car.setColorCar(pColorCar);
}
}
public class Director {
public Car buildNewCar(BaseCarBuilder carBuilder, CategoryCar pCategoryCar,
CarcassType pCarcassType, int pDoors, Engine pEngine,
ColorCar pColorCar) {
// Вот, теперь к главному. Вы неправильно поняли Builder по Gang of
// Four, и поэтому получилась чушь. При изучении паттернов один из самых
// важных принципов, как бы банально это не звучало, заключается в том,
// чтобы *четко* следовать тому, что написано. Давайте внимательно
// посмотрим на UML-диаграмму для паттерна Builder (http://goo.gl/rX9gMN).
// Сколько параметров у метода Director.construct? Один. А у вас сколько?
// Сколько параметров у метода Builder.buildPart? Ноль. А у вас сколько?
// А что это значит? Правильно, что вы неверно истолковали суть этого
// паттерна.
// В паттерне Builder важен следующий инвариант: как только объект
// ConcreteBuilder создан, он более не допускает параметризации извне.
// Идея заключается в том, что конкретный экземпляр Builder умеет
// каким-то своим способом совершать "маленькие шажки" к созданию
// объекта (buildPart), а Director умеет объединять шажки из интерфейса
// Builder таким образом, чтобы получился объект.
// Конкретно в вашем случае детали типа Engine.PETROL и
// CategoryCar.SPORT_CAR должны были быть "зашиты" в реализации класса
// MersedesBenzBuilder. А у вас получилась такая штука, что вы вместо
// Builder'a сделали крайне неочевидный named constructor, который
// весело перебрасывает аргументы туда-сюда.
// Годную реализацию этого паттерна на Java можете посмотреть здесь —
// http://goo.gl/KRk1L
// Также рекомендую вам ознакомиться с паттерном Fluent Builder —
// http://goo.gl/HIulBb
// И да, паттерны обычно тем или иным способом решают какую-то
// прикладную задачу. Поэтому "приступать к изучению паттернов," не
// имея под рукой хорошей задачи — это довольно плохая идея. Придумайте
// себе хорошую задачу и пытайтесь применять паттерны для ее решения, а
// не придумывайте задачи, чтобы применить паттерны.
// Хороший пример такой задачи как раз и приводится в книге GoF
// (http://www.amazon.com/dp/0201633612), где они step-by-step
// разрабатывают текстовый редактор.
Car tempCar = null;
if (carBuilder != null) {
carBuilder.buildCategory(pCategoryCar);
carBuilder.buildCarcass(pCarcassType);
carBuilder.buildDoors(pDoors);
carBuilder.buildEngine(pEngine);
carBuilder.buildColorCarcass(pColorCar);
// Я, кстати, вообще не понимаю, как это может работать, поскольку
// в вашем коде метод getBuiltCar всегда возвращает null.
tempCar = carBuilder.getBuiltCar();
} else {
// Простите, здесь у меня просто глаза вытекли.
final String nullPoint = "Yoa are not create car_builder";
Exception exception = new NullPointerException(nullPoint);
exception.printStackTrace();
}
return tempCar;
}
}
// Какое сказочное название для класса :)
public class RunBuilderPattern {
public static void main(String[] args) {
Car car = null;
Director director = new Director();
MersedesBenzBuilder mersedesBenzBuilder = new MersedesBenzBuilder();
// Ух ты! Смотрите! Я на самом деле работаю как простой конструктор,
// но два раза разворачиваю и заворачиваю параметры.
car = director.buildNewCar(
mersedesBenzBuilder,
CategoryCar.SPORT_CAR,
CarcassType.CABRIOLET,
Car.TWO_DOORS, Engine.PETROL,
ColorCar.RED);
System.out.println("CAR INFO: " + car.getCarInfo());
}
}
// ... а вот эти штуки неплохо бы переместить в класс Car.
public enum CarcassType {
SEDAN, UNIVERSAL, CABRIOLET, HATCHBACK, COUPE, LIMOUSINE
}
public enum CategoryCar {
CARGO, PASSENGER, BUS, SPORT_CAR
}
public enum ColorCar {
BLACK, WHITE, RED, GREEN, BLUE, ORANGE, BROWN
}
public enum Engine {
DIZEL, PETROL, GAZ, HUBRID
}