|
| 1 | +--- |
| 2 | +title: Abstract Factory Pattern |
| 3 | +created: 2025-04-22 |
| 4 | +tags: |
| 5 | + - creational |
| 6 | +--- |
| 7 | +## Definition |
| 8 | + |
| 9 | +The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete class. |
| 10 | + |
| 11 | +--- |
| 12 | +## Real World Analogy |
| 13 | + |
| 14 | +In the previous example of the [[Factory Method Pattern]], we demonstrated ordering pizzas through a `PizzaStore`. However, each store may use different local ingredients, dough, sauce, and cheese, so a pizza with the same name can taste different in different regions. For instance, a New York style pizza uses thin crust dough and marinara sauce, while a Chicago style pizza uses thick crust dough and plum tomato sauce. |
| 15 | + |
| 16 | +Using the Factory Method Pattern, we would need to create separate pizza classes for each store, even though the pizza types (like `CheesePizza` or `SchezwanPizza`) are conceptually the same. The only difference between them is the regional ingredients. |
| 17 | + |
| 18 | +With the Abstract Factory Pattern, we introduce an abstract ingredient factory that the `PizzaStore` uses to create dough, sauce, and cheese. Each store provides its own concrete factory implementation. When ordering a pizza, the store’s factory applies the correct regional ingredients automatically. If a new store opens, you simply add its ingredient factory without modifying the pizza ordering logic. |
| 19 | + |
| 20 | +```java title="Dough.java" |
| 21 | +// Interface for creating Dough |
| 22 | +interface Dough { |
| 23 | + public String getName(); |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +```java title="Sauce.java" |
| 28 | +// Interface for creating Sauce based on regional style |
| 29 | +interface Sauce { |
| 30 | + public String getSauce(); |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +```java title="Cheese.java" |
| 35 | +// Interface for creating Cheese based on regional style |
| 36 | +interface Cheese { |
| 37 | + public String getCheese(); |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +These interfaces define the contracts for dough, sauce, and cheese. Below we implement them for New York style and Chicago style ingredients. |
| 42 | + |
| 43 | +```java title="NYPizzaIngredients.java" |
| 44 | +// Creating Dough, Sauce, and Cheese based on New York style |
| 45 | +class ThinCrustDough implements Dough { |
| 46 | + @Override |
| 47 | + public String getName() { |
| 48 | + return "ThinCrustDough - NewYorkStore"; |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +class ReggianoCheese implements Cheese { |
| 53 | + @Override |
| 54 | + public String getCheese() { |
| 55 | + return "ReggianoCheese - NewYorkStore"; |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +class MarinaraSauce implements Sauce { |
| 60 | + @Override |
| 61 | + public String getSauce() { |
| 62 | + return "MarinaraSauce - NewYorkStore"; |
| 63 | + } |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +```java title="ChicagoPizzaIngredient.java" |
| 68 | +// Creating Dough, Sauce, and Cheese for the Chicago Pizza Store |
| 69 | +class MozzarellaCheese implements Cheese { |
| 70 | + @Override |
| 71 | + public String getCheese() { |
| 72 | + return "MozzarellaCheese - ChicagoStore"; |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +class ThickCrustDough implements Dough { |
| 77 | + @Override |
| 78 | + public String getName() { |
| 79 | + return "ThickCrustDough - ChicagoStore"; |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +class PlumTomatoSauce implements Sauce { |
| 84 | + @Override |
| 85 | + public String getSauce() { |
| 86 | + return "PlumTomatoSauce - ChicagoStore"; |
| 87 | + } |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +Here we implement New York style and Chicago style ingredients by implementing the interfaces. You could also group all regional implementations in a single file if you prefer. |
| 92 | + |
| 93 | +```java title="PizzaIngredientFactory.java" |
| 94 | +// Creating the interface for PizzaIngredientFactory |
| 95 | +interface PizzaIngredientFactory { |
| 96 | + public Dough createDough(); |
| 97 | + public Sauce createSauce(); |
| 98 | + public Cheese createCheese(); |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +We now create the `PizzaIngredientFactory` abstraction, which defines methods to produce each ingredient. Next we implement concrete factories for New York and Chicago styles. |
| 103 | + |
| 104 | +```java title="NyPizzaIngredientFactory.java" |
| 105 | +// PizzaIngredientFactory implementation for NYPizzaStore |
| 106 | +class NYPizzaIngredientFactory implements PizzaIngredientFactory { |
| 107 | + @Override |
| 108 | + public Dough createDough() { |
| 109 | + return new ThinCrustDough(); |
| 110 | + } |
| 111 | + |
| 112 | + @Override |
| 113 | + public Sauce createSauce() { |
| 114 | + return new MarinaraSauce(); |
| 115 | + } |
| 116 | + |
| 117 | + @Override |
| 118 | + public Cheese createCheese() { |
| 119 | + return new ReggianoCheese(); |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +```java title="ChicagoPizzaIngredientFactory.java" |
| 125 | +// PizzaIngredientFactory implementation for ChicagoPizzaStore |
| 126 | +class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory { |
| 127 | + @Override |
| 128 | + public Dough createDough() { |
| 129 | + return new ThickCrustDough(); |
| 130 | + } |
| 131 | + |
| 132 | + @Override |
| 133 | + public Sauce createSauce() { |
| 134 | + return new PlumTomatoSauce(); |
| 135 | + } |
| 136 | + |
| 137 | + @Override |
| 138 | + public Cheese createCheese() { |
| 139 | + return new MozzarellaCheese(); |
| 140 | + } |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +Next we update the `Pizza` abstract class so each pizza can prepare itself using the provided ingredient factory. |
| 145 | + |
| 146 | +```java title="Pizza.java" |
| 147 | +// Base class for creating a Pizza |
| 148 | +abstract class Pizza { |
| 149 | + public String name; |
| 150 | + public Dough dough; |
| 151 | + public Sauce sauce; |
| 152 | + public Cheese cheese; |
| 153 | + |
| 154 | + // Each pizza must prepare itself with regional ingredients |
| 155 | + abstract public void prepare(); |
| 156 | + |
| 157 | + // Common baking step |
| 158 | + public void bake() { |
| 159 | + System.out.println("Baking " + name); |
| 160 | + } |
| 161 | + |
| 162 | + // Common cutting step |
| 163 | + public void cut() { |
| 164 | + System.out.println("Cutting " + name); |
| 165 | + } |
| 166 | + |
| 167 | + // Common boxing step |
| 168 | + public void box() { |
| 169 | + System.out.println("Boxing " + name); |
| 170 | + } |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +The abstract `prepare` method enforces that each pizza subclass defines its own preparation steps. The `bake`, `cut`, and `box` methods provide shared behavior. |
| 175 | + |
| 176 | +```java title="CheesePizza.java" |
| 177 | +// Cheese Pizza class |
| 178 | +class CheesePizza extends Pizza { |
| 179 | + // Reference to the ingredient factory |
| 180 | + private PizzaIngredientFactory ingredientFactory; |
| 181 | + |
| 182 | + public CheesePizza(PizzaIngredientFactory factory) { |
| 183 | + this.ingredientFactory = factory; |
| 184 | + this.name = "Cheese Pizza"; |
| 185 | + } |
| 186 | + |
| 187 | + @Override |
| 188 | + public void prepare() { |
| 189 | + System.out.println("Preparing " + name); |
| 190 | + this.dough = ingredientFactory.createDough(); |
| 191 | + this.sauce = ingredientFactory.createSauce(); |
| 192 | + this.cheese = ingredientFactory.createCheese(); |
| 193 | + } |
| 194 | +} |
| 195 | +``` |
| 196 | + |
| 197 | +```java title="SchezwanPizza.java" |
| 198 | +// Schezwan Pizza class |
| 199 | +class SchezwanPizza extends Pizza { |
| 200 | + // Reference to the ingredient factory |
| 201 | + private PizzaIngredientFactory ingredientFactory; |
| 202 | + |
| 203 | + public SchezwanPizza(PizzaIngredientFactory factory) { |
| 204 | + this.ingredientFactory = factory; |
| 205 | + this.name = "Schezwan Pizza"; |
| 206 | + } |
| 207 | + |
| 208 | + @Override |
| 209 | + public void prepare() { |
| 210 | + System.out.println("Preparing " + name); |
| 211 | + this.dough = ingredientFactory.createDough(); |
| 212 | + this.sauce = ingredientFactory.createSauce(); |
| 213 | + this.cheese = ingredientFactory.createCheese(); |
| 214 | + } |
| 215 | +} |
| 216 | +``` |
| 217 | + |
| 218 | +In each pizza class we store the `PizzaIngredientFactory` passed into the constructor. This factory determines which regional ingredients will be used during preparation. |
| 219 | + |
| 220 | +Each `PizzaStore` subclass injects its own ingredient factory when creating pizzas: |
| 221 | + |
| 222 | +```java title="NyPizzaStore.java" |
| 223 | +// New York Pizza Store using NY ingredient factory |
| 224 | +class NyPizzaStore extends PizzaStore { |
| 225 | + private PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory(); |
| 226 | + |
| 227 | + @Override |
| 228 | + protected PizzaBase createPizza(String type) { |
| 229 | + if (type.equalsIgnoreCase("cheese")) { |
| 230 | + return new CheesePizza(ingredientFactory); |
| 231 | + } else if (type.equalsIgnoreCase("schezwan")) { |
| 232 | + return new SchezwanPizza(ingredientFactory); |
| 233 | + } |
| 234 | + return null; |
| 235 | + } |
| 236 | +} |
| 237 | +``` |
| 238 | + |
| 239 | +```java title="ChicagoPizzaStore.java" |
| 240 | +// Chicago Pizza Store using Chicago ingredient factory |
| 241 | +class ChicagoPizzaStore extends PizzaStore { |
| 242 | + private PizzaIngredientFactory ingredientFactory = new ChicagoPizzaIngredientFactory(); |
| 243 | + |
| 244 | + @Override |
| 245 | + protected PizzaBase createPizza(String type) { |
| 246 | + if (type.equalsIgnoreCase("cheese")) { |
| 247 | + return new CheesePizza(ingredientFactory); |
| 248 | + } else if (type.equalsIgnoreCase("schezwan")) { |
| 249 | + return new SchezwanPizza(ingredientFactory); |
| 250 | + } |
| 251 | + return null; |
| 252 | + } |
| 253 | +} |
| 254 | +``` |
| 255 | + |
| 256 | +Finally, in our client code we order pizzas from different stores to see the Abstract Factory in action: |
| 257 | + |
| 258 | +```java title="Program.java" |
| 259 | +// Ordering pizza from the New York store |
| 260 | +PizzaStore1 nyStore = new NyPizzaStore1(); |
| 261 | +nyStore.orderPizza("cheese"); |
| 262 | + |
| 263 | +// Ordering pizza from the Chicago store |
| 264 | +PizzaStore1 chicagoStore = new ChicagoPizzaStore1(); |
| 265 | +chicagoStore.orderPizza("schezwan"); |
| 266 | +``` |
| 267 | + |
| 268 | +**Sample Output:** |
| 269 | + |
| 270 | +``` |
| 271 | +Preparing Cheese Pizza |
| 272 | +Baking Cheese Pizza |
| 273 | +Cutting Cheese Pizza |
| 274 | +Boxing Cheese Pizza |
| 275 | +Preparing Schezwan Pizza |
| 276 | +Baking Schezwan Pizza |
| 277 | +Cutting Schezwan Pizza |
| 278 | +Boxing Schezwan Pizza |
| 279 | +``` |
| 280 | + |
| 281 | +## Design |
| 282 | + |
| 283 | +```mermaid |
| 284 | +classDiagram |
| 285 | + class Dough { |
| 286 | + <<Interface>> |
| 287 | + +String getName() |
| 288 | + } |
| 289 | + class Sauce { |
| 290 | + <<Interface>> |
| 291 | + +String getSauce() |
| 292 | + } |
| 293 | + class Cheese { |
| 294 | + <<Interface>> |
| 295 | + +String getCheese() |
| 296 | + } |
| 297 | + class ThinCrustDough { |
| 298 | + +String getName() |
| 299 | + } |
| 300 | + class ThickCrustDough { |
| 301 | + +String getName() |
| 302 | + } |
| 303 | + class MarinaraSauce { |
| 304 | + +String getSauce() |
| 305 | + } |
| 306 | + class PlumTomatoSauce { |
| 307 | + +String getSauce() |
| 308 | + } |
| 309 | + class ReggianoCheese { |
| 310 | + +String getCheese() |
| 311 | + } |
| 312 | + class MozzarellaCheese { |
| 313 | + +String getCheese() |
| 314 | + } |
| 315 | + Dough <|.. ThinCrustDough |
| 316 | + Dough <|.. ThickCrustDough |
| 317 | + Sauce <|.. MarinaraSauce |
| 318 | + Sauce <|.. PlumTomatoSauce |
| 319 | + Cheese <|.. ReggianoCheese |
| 320 | + Cheese <|.. MozzarellaCheese |
| 321 | +``` |
| 322 | + |
| 323 | +```mermaid |
| 324 | +classDiagram |
| 325 | + class PizzaIngredientFactory { |
| 326 | + <<Interface>> |
| 327 | + +createDough() Dough |
| 328 | + +createSauce() Sauce |
| 329 | + +createCheese() Cheese |
| 330 | + } |
| 331 | + class NYPizzaIngredientFactory { |
| 332 | + +createDough() Dough |
| 333 | + +createSauce() Sauce |
| 334 | + +createCheese() Cheese |
| 335 | + } |
| 336 | + class ChicagoPizzaIngredientFactory { |
| 337 | + +createDough() Dough |
| 338 | + +createSauce() Sauce |
| 339 | + +createCheese() Cheese |
| 340 | + } |
| 341 | + PizzaIngredientFactory <|.. NYPizzaIngredientFactory |
| 342 | + PizzaIngredientFactory <|.. ChicagoPizzaIngredientFactory |
| 343 | +``` |
| 344 | + |
| 345 | +```mermaid |
| 346 | +classDiagram |
| 347 | + class PizzaBase { |
| 348 | + <<Abstract>> |
| 349 | + + name: string |
| 350 | + + dough: Dough |
| 351 | + + sauce: Sauce |
| 352 | + + cheese: Cheese |
| 353 | + + prepare() |
| 354 | + + bake() |
| 355 | + + cut() |
| 356 | + + box() |
| 357 | + } |
| 358 | + class CheesePizza { |
| 359 | + +CheesePizza(factory: PizzaIngredientFactory) |
| 360 | + +void prepare() |
| 361 | + } |
| 362 | + class SchezwanPizza { |
| 363 | + +SchezwanPizza(factory: PizzaIngredientFactory) |
| 364 | + +void prepare() |
| 365 | + } |
| 366 | + class PizzaStore { |
| 367 | + <<Abstract>> |
| 368 | + +orderPizza(type: String) |
| 369 | + #createPizza(type: String): PizzaBase |
| 370 | + } |
| 371 | + class NyPizzaStore { |
| 372 | + -PizzaIngredientFactory ingredientFactory |
| 373 | + } |
| 374 | + class ChicagoPizzaStore { |
| 375 | + -PizzaIngredientFactory ingredientFactory |
| 376 | + } |
| 377 | + PizzaBase <|-- CheesePizza |
| 378 | + PizzaBase <|-- SchezwanPizza |
| 379 | + PizzaStore <|-- NyPizzaStore |
| 380 | + PizzaStore <|-- ChicagoPizzaStore |
| 381 | + NyPizzaStore *-- PizzaIngredientFactory |
| 382 | + ChicagoPizzaStore *-- PizzaIngredientFactory |
| 383 | + PizzaStore ..> PizzaBase |
| 384 | +``` |
| 385 | + |
| 386 | +--- |
| 387 | +## Design Principles |
| 388 | + |
| 389 | +- **Encapsulate What Varies** - Identify the parts of the code that are going to change and encapsulate them into separate class just like the Strategy Pattern. |
| 390 | +- **Favor Composition Over Inheritance** - Instead of using inheritance on extending functionality, rather use composition by delegating behavior to other objects. |
| 391 | +- **Program to Interface not Implementations** - Write code that depends on Abstractions or Interfaces rather than Concrete Classes. |
| 392 | +- **Strive for Loosely coupled design between objects that interact** - When implementing a class, avoid tightly coupled classes. Instead, use loosely coupled objects by leveraging abstractions and interfaces. This approach ensures that the class does not heavily depend on other classes. |
| 393 | +- **Classes Should be Open for Extension But closed for Modification** - Design your classes so you can extend their behavior without altering their existing, stable code. |
| 394 | +- **Depend on Abstractions, Do not depend on concrete class** - Rely on interfaces or abstract types instead of concrete classes so you can swap implementations without altering client code. |
0 commit comments