Factory Pattern: Motivation • Consider the Following Scenario: – You Have
Total Page:16
File Type:pdf, Size:1020Kb
Factory Pattern: Motivation • Consider the following scenario: { You have a superclass (P roduct) and a set of subclasses that inherit from it (P roductSubclass1, P roductSubclass2, ...) { As usual, there are a number of methods that the subclasses share in com- mon, that they inherit from the superclass { You have client code that needs to be able to create objects of the various subclasses ∗ We'll call this the orderProduct method ∗ We'll refer to the created objects as products ∗ To specify to which subclass a product should belong, orderProduct will have a parameter which specifies the type 1 Factory Pattern: Motivation (2) { For example: public class Product { //constructor ... ... step1(...) { //Methods that apply to all subclasses in common ... } ... step2(...) { ... } ... } public class ProductSubclass1 extends Product { ... } public class ProductSubclass2 extends Product { ... } public class ProductClient { ... Product orderProduct (String type) { Product product; if (type.equals("subclass1")) product = new ProductSubclass1(); else if (type.equals("subclass2")) product = new ProductSubclass2(); ... product.step1(); product.step2(); ... return product; } ... } • The problem with this approach - as usual - is limited flexibility { If we want to add new subclasses - types of products - (or remove existing ones), the client's code must be altered { The above design violates the Open/Closed Principle, among others 2 Factory Pattern: The Simple Factory Model • We want to encapsulate what varies { The part that varies is the product creation - We want to encapsulate the code that creates new products { To accomplish this, we'll create a new class that we'll call a factory { It's purpose is to create new products of a desired type { The client will have an instance variable for the factory ∗ The factory will be used from within the orderP roduct method of the client to create products 3 Factory Pattern: The Simple Factory Model (2) • Sample code: //Superclass and subclasses as before public class ProductSubclass1 extends Product { ... } public class ProductSubclass2 extends Product { ... } public class SimpleFactory { public Product createSubclassProduct (String type) { Product product null; if (type.equals("subclass1")) product = new ProductSubclass1(); else if (type.equals("subclass2")) product = new ProductSubclass2(); ... return product; } public class Client { SimpleFactory factory; public Client (SimpleFactory factory) { this.factory = factory;) } Product orderProduct (String type) { Product product; product = factory.createSubclassProduct(type); product.step1(); product.step2(); ... return product; } ... } 4 Factory Pattern: The Simple Factory Model (3) • Summary of the changes: 1. Create a new factory class { The factory implements the method for creating new products { This code had originally been in the client 2. Remove the product creation method from the client { Replace it with a call to the product creation method in the factory 3. Add an instance variable for the factory in the client { Its value is passed in via the constructor • Class diagram: • Simple Factory is not a true pattern 5 Factory Pattern: Further Considerations • Suppose you now want to have different types of clients { Each client type produces the same set of products, but { Each product of a given subclass varies in some way from client type to client type • We can think of each client type as representing a category for the subclasses { For example , there is a 1. A category1 for ProductSubclass1, ProductSubclass2, ...; 2. A category2 for ProductSubclass1, ProductSubclass2, ..., etc. ∗ Each category makes products for the same set of subclasses ∗ But each category produces them with slightly different characteristics { For the subclasses, some inherited methods may be implemented exactly the same no matter what category a product belongs to { Other methods may differ based on the category of the product**** • One approach would be to have a factory for each category { A factory for category1 { A factory for category2 { ... 6 Factory Pattern: Further Considerations (2) • Class diagram: • Sample driver code: Category1Factory factory1 = new Category1Factory(); Client cat1Client = new Client(factory1); cat1Client.orderProduct("class1"); Category2Factory factory2 = new Category2Factory(); Client cat2Client = new Client(factory2); cat2Client.orderProduct("class1"); 7 Factory Pattern: Further Considerations (3) { In the above example ∗ Two factories are created: 1. One for creating category1 products 2. One for creating category2 products ∗ Two objects are created: 1. cat1Client, which "orders" class1-type products from a category1 fac- tory 2. cat2Client, which "orders" class1-type products from a category2 fac- tory ∗ Both clients end up with class1 products, but they differ as per the category to which they belong • A better approach would be to link the product creation back to the client, but with more flexibility than our original approach had { To achieve the flexibilty of having product creation back in the client: 1. Make an abstract class for clients (Client) 2. Move the createP roduct method back into the Client class ∗ This method becomes abstract here ∗ With the createP roduct back in the Client class, Client becomes a superclass for factories 3. Make a client subclass for each category (Category1F actory, Category2F actory, ...) ∗ These subclasses inherit from Client ∗ Since createP roduct is abstract in Client, each of these subclasses must implement it ∗ These implementations will reflect the variations peculiar to the cat- egory ∗ The subclasses are the actual factories 4. We still have a generic P roduct class 5. Create a subclass for each combination of product subclass and category ∗ The subclasses all inherit from the P roduct class 8 Factory Pattern: Further Considerations (4) { Class diagram: 9 Factory Pattern: Further Considerations (5) { Code: public abstract class Client { public Product orderProduct(String type) { Product product; product = createProduct(type); product.step1(...); product.step2(...); ... return product; } abstract Product createProduct(String type); } public abstract class Product { ... step1(...) { //Methods that apply to all subclasses in common ... } ... step2(...) { ... } ... } public class Category1Factory extends Client { ... Product createProduct (String type) { if (type.equals("subclass1")) return new Category1Subclass1Product(); else if (type.equals("subclass2")) return new Category1Subclass2Product(); ... else return null; } ... } 10 Factory Pattern: Further Considerations (6) public class Category1Subclass1Product extends Product { //constructor //Override any steps that are specific to Catgory1Subclass1 products ... } public class Driver { public static void main (String[] arge) { Client cat1Factory = new Category1Factory(); Client cat2Factory = new category2Factory(); Product product = cat1Factory.orderProduct("type2"); product = cat2Factory.orderProduct("type2"); ... } 11 Factory Pattern: The Factory Method Pattern Defined • The Factory Pattern (p 134): Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantia- tion to subclasses. • Class diagram: • The pattern encapsulates the instantiation of concrete types 1. The Creator class provides an interface for creating products { It defines the factory structure 2. The concrete subclasses of Creator actually generate the products 3. Methods in Creator other than factoryMethod are used only to operate on the products • "Deciding by subclasses" is a bit misleading { It's the user/programer who actually decides what subclass will be instan- tiated { The use of "decides" is meant to imply loose coupling between the client and Creator { Creator doesn't know what products are being created 12 Factory Pattern: The Factory Method Pattern Defined (2) • Even when only a single concrete creator is needed, the Factory Method is warranted { It decouples the implementation of a product from its use { It facilitates addition of additional concrete creators { It facilitates modification of implementation • While similar to the Simple Factory, Factory is different { In Simple Factory, the factory is composed with the client { In Factory, each client must implement the factory method • Creator and factoryMethod can be concrete { This allows creation of products even when there are no subclasses • There can be a single type of product rather than variations (types) { In such cases, no parameter needs to be passed to the factory method 13 Factory Pattern: Dependency Inversion Principle • The Dependency Inversion Principle (p 139) (Principle 6) Depend upon abstractions. Do not depend upon concrete classes. • It imposes a stricter condition than the "Program to an interface, not an im- plementation" Principle { High-level components should not depend on low-level ones { Both should depend on abstractions { This is an "inversion" of the usual type of thinking in OO design • In our original approach (Factory Pattern Motivation), if we had taken into account categories of products, we would have ended up with code similar to public class Client { public Product createProduct (String category, String type) { Product product = null; if (category.equals("category1")) if (type.equals("type1")) product = new Category1Subclass1(); else if (type.equals("type2")) product = new Category1Subclass2(); ... if (category.equals("category2")) if (type.equals("type1")) product = new Category2Subclass1(); else