Design Mode note 04-factory Mode

Source: Internet
Author: User

Design Mode note 04-factory Mode
1 Introduction
In addition to the new operator, there are more ways to create objects. You will understand that the instantiation activity should not always be made public, and initialization often causes Coupling Problems. You don't want this, do you? Read on to learn how the factory model helps you get rid of the complex dependencies.
2 Text
2.1 What's Wrong With new?
When there is a group of specific classes, such code is usually written:

Duck duck;if (picnic){    duck = new MallardDuck();}else if (hunting){    duck = new DecoyDuck();}else if (inBathTub){    duck = new RubberDuck();}

Here are some specific classes to be instantiated. The actual class to be instantiated should be determined by some conditions at runtime. Once such code is changed or expanded, you must re-open the code to check and modify it. Code modified in this way will make some systems more difficult to maintain and update, and more prone to mistakes.
However, you always need to create an object! Java only provides one new keyword to create an object, isn't it? What else can I do? Technically, there is no new error. After all, this is the basic part of Java. The real prisoner is our old friend "change" and how it affects the use of new. For interface programming, you can isolate a lot of changes that may occur in the system in the future. Why? If the code is written for an interface, it can implement this interface with any new class through polymorphism. However, when the Code uses a large number of specific classes, it is difficult because the Code must be changed once a new specific class is added. That is to say, your code is not "Closed for modification". To extend the Code with a new type, you must open it again. So what should we do? When such a problem occurs, we should return to the OO design principles to find clues. Don't forget, our first principle is to deal with changes and help us "identify the changing aspects and separate them from the changing ones".

2.2 identifying changes
Suppose you have another pizza shop. As the most advanced pizza director in the country, your code may be written like this.
Public Pizza orderPizza (String type) {Pizza pizza, here, you must modify the if (type. equals ("cheese") {pizza = new CheesePizza ();} else if {}// this is something we don't want to change. Because the preparation, baking, slicing, and packaging of pizza have remained unchanged for many years, // This part of the code will not change. Only the pizza with these actions will change pizza. prepare (); pizza. bake (); pizza. cut (); pizza. box (); return pizza ;}

Obviously, if you instantiate "some" specific classes, it will cause an issue with orderPizza (), and it will not be able to disable orderPizza (). But now we know what changes will happen, which will not change, it is time to try the package.
2.3 create a simple Pizza Factory
First, extract the code for creating an object from orderPizza (), and then move the code to another object. This new object only involves how to create a pizza. If any object wants to create a pizza, find it.
Public class SimplePizzaFactory {// SimplePizzaFactory only does one thing, helping its customers create Pizza // first, define a createPizza () method in this factory. All customers use this method to instance the new object public Pizza createPizza (String type) {Pizza pizza = null; // The code is unchanged, just like the code in the original orderPizza () method, if (type. equals ("cheese") {pizza = new CheesePizza ();} else if (type. equals ("pepperoni") {pizza = new PepperoniPizza ();} else if (type. equals ("clam") {pizza = new ClamPizza ();} else if (type. equals ("veggie") {pizza = new VeggiePizza ();} return pizza ;}}

In addition, it is a common technique to use static methods to define a simple factory, which is often called a static factory. Why do I use static methods? Because you do not need to use the object creation method to instantiate an object. But remember, this also has some disadvantages. You cannot use inheritance to change the creation method behavior.
Redo the PizzaStore class
Public class PizzaStore {// Add a reference to SimplePizzaFactory factory to PizzaStore; // The constructor of PizzaStore. A factory is required as the parameter public PizzaStore (SimplePizzaFactory factory) {this. factory = factory;} public Pizza orderPizza (String type) {Pizza pizza; // while the orderPizza () method creates pizza using the factory by simply passing in the order type. // note that, we will replace the new operator with the creation method of the factory object, and we will not use the specific instantiation here! Pizza = factory. createPizza (type); pizza. prepare (); pizza. bake (); pizza. cut (); pizza. box (); return pizza ;}}

2.4 define a simple factory
A simple factory is not a design pattern, but rather a programming habit. Some developers mistakenly think of this programming habit as "Factory Patter ). Do not ignore the use of a simple factory because it is not a "real" design model. Thank you for keeping us warm. Next we will present two heavyweight models, both of which are factories.

2.5 factory method mode
The operation of the object Village Pizza store is also called. Now everyone hopes that the object Village Pizza can have a franchise store nearby. One of the stores wanted the factory to make New York-style Pizza: pancakes, delicious sauces, and a small amount of cheese. Another stores wanted to make Chicago-style Pizza: thick cakes, savory sauces, and a large amount of cheese. If SimplePizzaFactory is used to write out three unused factories, the franchisees in different regions have their own suitable factories. This is a practice. During the promotion of SimpleFactory, you found that the franchise stores are indeed creating Pizza using your factory, but other parts are starting to adopt their own processes: there are some differences in baking practices, do not slice, use boxes from other vendors. If you think about this, you really want to build a framework to bind the franchise stores with the creation of pizza and maintain a certain degree of elasticity.

Design of factory methods
The public abstract class PizzaStore {/** factory method is used to process object creation and encapsulate such behavior in sub-classes. * In this way, the super class code in the client program is decoupled from the Code created for the subclass object. * 1. The factory method is abstract, so the subclass is used to process object creation. * 2. The factory method must return a product. The methods defined in the superclass usually use the return value of the factory method; * 3. The factory method treats the customer (that is, the code in the superclass, such as orderPizza ()) separated from the code of the actual product creation */abstract Pizza createPizza (String item); public Pizza orderPizza (String type) {Pizza pizza = createPizza (type); System. out. println ("--- Making a" + pizza. getName () + "---"); pizza. prepare (); pizza. bake (); pizza. cut (); pizza. box (); return pizza ;}}


I just ignored an important thing, Pizza itself.
Public abstract class Pizza {// each Pizza has a name, dough type, sauce type, a set of seasoning String name; String dough; String sauce; ArrayList toppings = new ArrayList (); void prepare () {System. out. println ("Preparing" + name); System. out. println ("Tossing dough... "); System. out. println ("Adding sauce... "); System. out. println ("Adding toppings:"); for (int I = 0; I <toppings. size (); I ++) {System. out. println ("" + toppings. get (I) ;}} void bake () {System. out. println ("Bake for 25 minutes at 350");} void cut () {System. out. println ("Cutting the pizza into diagonal slices");} void box () {System. out. println ("Place pizza in official PizzaStore box");} public String getName () {return name;} public String toString () {StringBuffer display = new StringBuffer (); display. append ("----" + name + "---- \ n"); display. append (dough + "\ n"); display. append (sauce + "\ n"); for (int I = 0; I <toppings. size (); I ++) {display. append (String) toppings. get (I) + "\ n");} return display. toString ();}}

The following lists some specific pizza subclasses.
public class NYStyleCheesePizza extends Pizza {public NYStyleCheesePizza() { name = "NY Style Sauce and Cheese Pizza";dough = "Thin Crust Dough";sauce = "Marinara Sauce"; toppings.add("Grated Reggiano Cheese");}}

Public class shortextends Pizza {public chicagstylecheesepizza () {name = "Chicago Style Deep Dish Cheese Pizza"; dough = "Extra Thick Crust Dough"; sauce = "Plum Tomato Sauce "; toppings. add ("Shredded Mozzarella Cheese");} // This Pizza overwrites the cut () method and splits Pizza into square void cut () {System. out. println ("Cutting the pizza into square slices ");}}

Specific PizzaStroe class
public class NYPizzaStore extends PizzaStore {Pizza createPizza(String item) {if (item.equals("cheese")) {return new NYStyleCheesePizza();} else if (item.equals("veggie")) {return new NYStyleVeggiePizza();} else if (item.equals("clam")) {return new NYStyleClamPizza();} else if (item.equals("pepperoni")) {return new NYStylePepperoniPizza();} else return null;}}

Test class
public class PizzaTestDrive { public static void main(String[] args) {PizzaStore nyStore = new NYPizzaStore();PizzaStore chicagoStore = new ChicagoPizzaStore(); Pizza pizza = nyStore.orderPizza("cheese");System.out.println("Ethan ordered a " + pizza.getName() + "\n"); pizza = chicagoStore.orderPizza("cheese");System.out.println("Joel ordered a " + pizza.getName() + "\n");}


The time has come to understand the factory method model: All factory modes are used to encapsulate object creation. Factory Method Pattern encapsulates the object creation process by letting the subclass decide what the created object is.Let's take a look at the components: 1, Creator class): PizzaStore, NYPizzaStore, and ChicagoPizzaStorePizzaStore are abstract creators. They define an abstract factory method that allows subclasses to implement this method to create products. Creators usually include code that relies on abstract products, which are made by sub-classes. The Creator does not need to know which specific product to create. NYPizzaStore and ChicagoPizzaStore are specific creators. The createPizza () method is a real factory method used to manufacture products. Because each franchise store has its own PizzaStore subclass, you can use the createPizza () method to create your own flavor Pizza. 2, Product Type:Pizza and the product classes of each franchise store (Pizza sub-class) parallel class level: we have seen that combining an orderPizza () method with a factory method can become a framework. In addition, factory methods encapsulate production knowledge into various creators. Such an approach can also be considered as a framework.
Define factory method mode: The Factory method mode defines an interface for creating objects, but the subclass determines which class to instantiate. The factory method delays the class Instantiation to the subclass.According to the above PizzaStore division, we can easily understand that there are two very important parent classes, FactoryCreator (abstract interface) and Product (Product ), abstract The Factory method to make sub-classes implement manufacturing products (inherited from products ).
The difference between a simple factory and a factory method mode: A simple factory handles everything in one place. However, the factory method is to create a framework so that sub-classes decide how to implement it. For example, in the factory method, the orderPizza () method provides a general framework to create Pizza. The orderPizza () method relies on the factory method to create a specific class and create the actual Pizza. You can inherit the PizzaStore class to determine what the actual Pizza is. A simple factory can encapsulate object creation, but a simple factory does not have the elasticity of the factory method, because a simple factory cannot change the product being created.

2.6 Abstract Factory Model
Reducing dependencies on specific classes in the Code is a "good thing". In fact, an OO design Principle officially clarified this point. This Principle even has a resounding and formal name: Dependency Inversion Principle ). Dependency inversion principle: dependent on abstraction, not specific classes. First, this principle sounds like "programming for interfaces, not programming for implementation", isn't it? It is indeed very similar. However, abstraction is more emphasized here. This principle illustrates that a high-level component cannot depend on a lower-level component, and "both" must depend on abstraction regardless of the upper or lower-level component. A "high-level" component is a class that defines its behavior by other low-level components. For example, PizzaStore is a high-level component because its behavior is defined by Pizza: PizzaStore creates all different Pizza objects, preparing, baking, slicing, and loading boxes; pizza itself is a low-level component. What does this mean? Let's look at the relationship between this type and the product type. PizzaStore is a "high-level component", while Pizza is a "low-level component". It is clear that, pizzaStore depends on these specific Pizza classes.
Now, this principle tells us that we should rewrite the code so that we can rely on abstract classes instead of specific classes. This should be true for High-level and low-level modules.
Where is the principle of dependency inversion?The inversion in the dependency inversion principle refers to the opposite of the general OO design thinking method. The lower-level components depend on the high-level abstraction. Similarly, the higher-level components also depend on the same abstraction. The previous dependency is from top to bottom, but is now inverted, and the high-level and low-level modules are now dependent on this abstraction.
Several guidelines help you follow this principle: 1. Variables cannot hold references to specific classes. If new is used, a reference to a specific class is held. You can switch to a factory to avoid this practice. 2. Do not allow a class to be derived from a specific class. If it is derived from a specific class, you will depend on the specific class. Please derive from an abstract (interface or abstract class ). 3. Do not overwrite the implemented methods in the base class. If you override the implemented methods in the base class, your base class is not an abstract that is truly suitable for inheritance. The methods implemented in the base class should be shared by all subclasses.
We should try to achieve this principle, rather than always following this principle. We all know that any java program violates these guidelines. However, if you thoroughly experience these policies and internalize them as part of your thinking, you will guide when there are sufficient reasons to violate these principles during design. For example, if there is a class that does not seem to change, then directly instantiating a specific class in the code will be fine. For example, instantiate a String object. On the other hand, if a class may change, you can use some good techniques (such as the factory method) to encapsulate changes.
Returning to the Pizza store, the key to the success of the object Village Pizza store is fresh and high-quality raw materials. By importing a new framework, the franchise stores will follow your process, but there are some franchisees, use low-price raw materials to increase profits. You must take some measures to avoid damaging the brand of the target village. You plan to build a factory that produces raw materials and deliver the raw materials to the stores. The franchise stores are located in different regions and each region has different raw materials. Therefore, for different regions (New York and Chicago), you have prepared two groups of different raw materials.
Build a raw material factory
Public interface PizzaIngredientFactory {// In the interface, each raw material has a corresponding method to create the raw material // if each factory instance has a general "mechanism" to implement, this example can be rewritten to the abstract class public Dough createDough (); public Sauce createSauce (); public Cheese createCheese (); public Veggies [] createVeggies (); public Pepperoni createpperoni (); public Clams createClam ();}

New York Raw Material Factory
Public class NYPizzaIngredientFactory implements PizzaIngredientFactory {// for each type of raw material in the raw material family, we provide public Dough createDough () {return new ThinCrustDough ();} public Sauce createSauce () {return new MarinaraSauce ();} public Cheese createCheese () {return new ReggianoCheese ();} public Veggies [] createVeggies () {Veggies veggies [] = {new Garlic (), new Onion (), new Mushroom (), new RedPepper ()}; return veggies;} public Pepperoni createpperoni () {return new SlicedPepperoni ();} public Clams createClam () {return new FreshClams ();}}

Redo Pizza class
Public abstract class Pizza {String name; // each Pizza has a set of raw materials for preparation, such as Dough dough; Sauce sauce; Veggies veggies []; Cheese cheese; Pepperoni pepperoni; clams clam; // The prepare () method is abstracted. In this method, we need to collect the raw materials required for the pizza, which of course comes from the raw material factory. Abstract void prepare (); void bake () {System. out. println ("Bake for 25 minutes at 350");} void cut () {System. out. println ("Cutting the pizza into diagonal slices");} void box () {System. out. println ("Place pizza in official PizzaStore box");} void setName (String name) {this. name = name;} String getName () {return name;} public String toString () {StringBuffer result = new StringBuffer (); result. append ("----" + Name + "---- \ n"); if (dough! = Null) {result. append (dough); result. append ("\ n");} if (sauce! = Null) {result. append (sauce); result. append ("\ n");} if (cheese! = Null) {result. append (cheese); result. append ("\ n");} if (veggies! = Null) {for (int I = 0; I <veggies. length; I ++) {result. append (veggies [I]); if (I <veggies. length-1) {result. append (",") ;}} result. append ("\ n");} if (clam! = Null) {result. append (clam); result. append ("\ n");} if (pepperoni! = Null) {result. append (pepperoni); result. append ("\ n");} return result. toString ();}}

Now we don't need to design two different classes to deal with Pizza of different flavors, so that the raw material factory can handle this regional difference. Below is CheesePizza
Public class CheesePizza extends Pizza {PizzaIngredientFactory ingredientFactory; // the factory must provide raw materials for Pizza. Therefore, each Pizza class needs to get a factory from the constructor parameter, // and store this factory in an instance variable public CheesePizza (PizzaIngredientFactory ingredientFactory) {this. ingredientFactory = ingredientFactory;} // The magic thing is happening here. The prepare () method creates the cheese Pizza step by step. Whenever raw materials are required, void prepare () {System. out. println ("Preparing" + name); dough = ingredientFactory. createDough (); sauce = ingredientFactory. createSauce (); cheese = ingredientFactory. createCheese ();}}

Pizza's Code uses relevant factory production materials. The raw materials produced depend on the factory in use. The Pizza class does not care about the raw materials at all. It only knows how to make Pizza. Currently, Pizza is decoupled from Regional Raw Materials. no matter whether the raw material factory is in the Los Angeles mountains or the northwestern coast, Pizza can be easily reused and there is no problem at all. Return to the Pizza store:
Public class NYPizzaStore extends PizzaStore {protected Pizza createPizza (String item) {Pizza pizza = null; // New York stores will use the Pizza Factory in New York, the raw material factory is responsible for producing all the raw materials PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory ();/** transfer the factory to each Pizza so that raw materials can be obtained from the factory * for each Pizza, we instantiate a new Pizza and pass it to the factory required by Pizza so that Pizza can obtain its raw material */if (item. equals ("cheese") {pizza = new CheesePizza (ingredientFactory); pizza. setName ("New York Style Cheese Pizza");} else if (item. equals ("veggie") {pizza = new VeggiePizza (ingredientFactory); pizza. setName ("New York Style Veggie Pizza");} else if (item. equals ("clam") {pizza = new ClamPizza (ingredientFactory); pizza. setName ("New York Style Clam Pizza");} else if (item. equals ("pepperoni") {pizza = new PepperoniPizza (ingredientFactory); pizza. setName ("New York Style Pepperoni Pizza") ;}return pizza ;}}

What have we done? We introduce a new type of factory, the so-called abstract factory, to create a raw material family. Through the abstract interface provided by the factory, you can create a product family and use this interface to write code. Our code will be decoupled from the actual factory so that various factories can be implemented in different contexts, make a variety of products. For example, different regions, different operating systems, and different appearances and operations. Because the code is decoupled from the actual product, we can replace different factories to achieve different behaviors.
Define the abstract factory mode: provides an interface for creating families of related or dependent objects without specifying specific classes. Abstract Factory allows customers to use abstract interfaces to create a group of related products without knowing (or caring) What specific products are actually produced. In this way, the customer is decoupled from a specific product.

3. Summary of this Chapter
For a long time, we have learned simple factory, factory method mode, and abstract factory mode. The three have already confused us in terms of text definitions. The specific differences between the three can be understood only through the use of code farmers in practical applications. The following lists some key points of this chapter to see if you have really understood these concepts: 1. All factories are used to encapsulate object creation. 2. Simple factory, although not a real design model, is still a simple method that can decouple customer programs from specific classes. 3. Factory method use inheritance: delegates object creation to child classes, and child classes implement factory methods to create objects. 4. Abstract Factory use object combination: Object creation is implemented in the method exposed by the factory interface. 5. All factory models promote loose coupling by reducing dependencies between applications and specific classes. 6. factory methods allow classes to delay Instantiation to subclass. 7. Abstract factories create Related Object families without relying on their specific classes. 8. The dependency inversion principle guides us to avoid relying on specific types, but to rely on abstraction as much as possible. 9. Factory is a very powerful technique that helps us with abstract programming, rather than specific class programming.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.