Foundations of Software Engineering Design Patterns Creational DESIGN

  • Slides: 55
Download presentation
Foundations of Software Engineering Design Patterns: Creational

Foundations of Software Engineering Design Patterns: Creational

DESIGN PATTERNS Each pattern describes a problem which occurs over and over again in

DESIGN PATTERNS Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice. (Christopher Alexander et al. , “A Pattern Language”, 1977) 2

WHY DESIGN PATTERNS? Ubiquitous Language When one developer says “we can implement this using

WHY DESIGN PATTERNS? Ubiquitous Language When one developer says “we can implement this using a Strategy” and another says “. . . or maybe with State”, they communicate a lot of information with very little overhead. 3

WHY DESIGN PATTERNS? Don’t Reinvent the Wheel Many problems in software development have already

WHY DESIGN PATTERNS? Don’t Reinvent the Wheel Many problems in software development have already been solved, and the solution has been generalized enough so it is applicable everywhere. 4

WHY DESIGN PATTERNS? Best Practices Design patterns adhere to many best practices in object-oriented

WHY DESIGN PATTERNS? Best Practices Design patterns adhere to many best practices in object-oriented programming: programming to an interface, favoring composition over inheritance, and designing for change. Hopefully, these patterns will instill in us good habits in our programming careers. 5

CREATIONAL PATTERNS Creational design patterns abstract the instantiation process. They help make a system

CREATIONAL PATTERNS Creational design patterns abstract the instantiation process. They help make a system independent of how its objects are created, composed, and represented. (Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, “Design Patterns: Elements of Reusable Object-Oriented Software”, 1994) 6

AGENDA Today we will cover the following three creational patterns: • Factory Method •

AGENDA Today we will cover the following three creational patterns: • Factory Method • Builder • Prototype 7

Factory Method Pattern 8

Factory Method Pattern 8

INTENT Define an interface for creating an object, but let subclasses decide which class

INTENT Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. 9

PATTERN DIAGRAM 10

PATTERN DIAGRAM 10

PARTICIPANTS • Product – the interface of objects the factory method creates. • Concrete.

PARTICIPANTS • Product – the interface of objects the factory method creates. • Concrete. Product – implements the Product interface 11

PARTICIPANTS • Creator – an abstract class declaring the factory method. It may have

PARTICIPANTS • Creator – an abstract class declaring the factory method. It may have some business logic methods that use the factory method. • Concrete. Creator – overrides the factory method to return an instance of Concrete. Product. 12

EXAMPLE Our system can contact users with one of two contact methods: regular mail

EXAMPLE Our system can contact users with one of two contact methods: regular mail (“snail mail”) or Whats. App. We don’t want the client to depend on a concrete implementation so we can add more contact methods in the future. A perfect use-case for factory method. 13

EXAMPLE We start with an abstract Contact. Method class (the Product): public abstract class

EXAMPLE We start with an abstract Contact. Method class (the Product): public abstract class Contact. Method { private Priority priority = Priority. NORMAL; public abstract void contact(User user); public Priority get. Priority() { return priority; } public void set. Priority(Priority priority) { this. priority = priority; } } 14

EXAMPLE Contact. Method has two concrete implementations, here is one of them (the Concrete.

EXAMPLE Contact. Method has two concrete implementations, here is one of them (the Concrete. Product): public class Snail. Mail extends Contact. Method { @Override public void contact(User user) { System. out. printf("[%s] Contacting %s via the address: %sn", get. Priority(), user. get. User. Name(), user. get. Address()); } } 15

EXAMPLE Next, we implement the factory class, which will hold the factory method: public

EXAMPLE Next, we implement the factory class, which will hold the factory method: public abstract class Contact. Method. Factory { protected abstract Contact. Method create. Contact. Method(); public Contact. Method get. Contact. Method(User user) { Contact. Method contact. Method = create. Contact. Method(); if (user. get. Email(). ends. With("bgu. ac. il")) { contact. Method. set. Priority(Priority. HIGH); } return contact. Method; } } 16

EXAMPLE The abstract create. Contact. Method is the factory method. It is protected, because

EXAMPLE The abstract create. Contact. Method is the factory method. It is protected, because in this case, we want the client to go through get. Contact. Method to get a concrete Contact. Method and run the business logic in the factory. 17

EXAMPLE Let’s see a concrete implementation of a factory: public class Snail. Mail. Factory

EXAMPLE Let’s see a concrete implementation of a factory: public class Snail. Mail. Factory extends Contact. Method. Factory { @Override protected Contact. Method create. Contact. Method() { return new Snail. Mail(); } } Sometimes, that’s all we need for a factory: to give a concrete Product. 18

EXAMPLE Lastly, we need to code the client. The factory is a dependency of

EXAMPLE Lastly, we need to code the client. The factory is a dependency of the client, and again, we don’t want the client to have anything to do with concrete implementations. We will inject the factory in the client’s constructor: 19

EXAMPLE public class Client { private final Contact. Method. Factory factory; public Client(Contact. Method.

EXAMPLE public class Client { private final Contact. Method. Factory factory; public Client(Contact. Method. Factory factory) { this. factory = factory; } public void do. Work() { User user = new User(. . . ); Contact. Method contact. Method = factory. get. Contact. Method(user); contact. Method. contact(user); } } 20 1/51

EXAMPLE The root of the application (main method, for example) will be responsible of

EXAMPLE The root of the application (main method, for example) will be responsible of injecting the concrete factory, presumably based on some configuration. For this example, we’ll just hard-code the desired factory: 21

EXAMPLE public class Factory. Demo { public static void main(String[] args) { Contact. Method.

EXAMPLE public class Factory. Demo { public static void main(String[] args) { Contact. Method. Factory factory = new Whats. App. Factory(); Client client = new Client(factory); client. do. Work(); } } 22

THE PATTERN IN THE WILD An example of a Factory Method in the Java

THE PATTERN IN THE WILD An example of a Factory Method in the Java API is the Calendar class. In order to get an instance of Calendar, we will write: Calendar cal = Calendar. get. Instance(); Depending on our locale and timezone, we will get a differently configured Calendar object. 23

THE PATTERN IN THE WILD “Wait a second”, you might say, “get. Instance is

THE PATTERN IN THE WILD “Wait a second”, you might say, “get. Instance is a static method, this isn’t like the pattern!”. So yes and no: even though the class diagram might be different in the Calendar example, the intent is the same! We’re creating an instance of a concrete Calendar without having to know the details of construction. 24

FACTORIES VS. CONSTRUCTORS So why use a factory and not a constructor? In a

FACTORIES VS. CONSTRUCTORS So why use a factory and not a constructor? In a word: abstraction. The client seldom needs to know how a concrete product is instantiated, as long as the product adheres to some known interface. 25

FACTORIES VS. CONSTRUCTORS However, like every other design pattern, do not abuse the factory

FACTORIES VS. CONSTRUCTORS However, like every other design pattern, do not abuse the factory method pattern. Not every instantiation needs a factory. For example, sometimes the client knows exactly what it needs, and it doesn’t seem to need a layer of abstraction. Constructors will do just fine in those cases. 26

Builder Pattern 27

Builder Pattern 27

INTENT Separate the construction of a complex object from its representation so that the

INTENT Separate the construction of a complex object from its representation so that the same construction process can create different representations. 28

PATTERN DIAGRAM 29

PATTERN DIAGRAM 29

PARTICIPANTS • Builder – specifies an abstract interface for creating parts of a Product

PARTICIPANTS • Builder – specifies an abstract interface for creating parts of a Product object. • Concrete. Builder – constructs and assembles parts of the product by implementing the Builder interface, keeps track of the representation it creates, and provides an interface for retrieving the Product. 30

PARTICIPANTS • Director – constructs an object using the Builder interface. • Product –

PARTICIPANTS • Director – constructs an object using the Builder interface. • Product – represents the complex object under construction. 31

EXAMPLE Some of you are taking “Compiler Principles” course, and would like to construct

EXAMPLE Some of you are taking “Compiler Principles” course, and would like to construct a compiler. For our purposes, a compiler needs three things: a source language, a target language, and a parsing combinators library. We define our Compiler. Builder interface as follows: 32

EXAMPLE public interface Compiler. Builder { void set. Source. Language(Language source. Language); void set.

EXAMPLE public interface Compiler. Builder { void set. Source. Language(Language source. Language); void set. Target. Language(Language target. Language); void set. PCLibrary(PCLibrary library); } 33

EXAMPLE Next, we need our concrete builders: OCaml. Compiler. Builder and Python. Compiler. Builder.

EXAMPLE Next, we need our concrete builders: OCaml. Compiler. Builder and Python. Compiler. Builder. We will show one here: 34

EXAMPLE public class OCaml. Compiler. Builder implements Compiler. Builder { private Language source; private

EXAMPLE public class OCaml. Compiler. Builder implements Compiler. Builder { private Language source; private Language target; private PCLibrary pc. Library; @Override public void set. Source. Language(Language source. Language) { source = source. Language; } @Override public void set. Target. Language(Language target. Language) { target = target. Language; } @Override public void set. PCLibrary(PCLibrary library) { pc. Library = library; }. . . } 35

EXAMPLE public class OCaml. Compiler. Builder implements Compiler. Builder {. . . public OCaml.

EXAMPLE public class OCaml. Compiler. Builder implements Compiler. Builder {. . . public OCaml. Compiler build() { return new OCaml. Compiler(source, target, pc. Library); } } 36

EXAMPLE OCaml. Compiler is our Product. It’s not very complex with only three parameters

EXAMPLE OCaml. Compiler is our Product. It’s not very complex with only three parameters in the constructor, but imagine it had many more fields, some maybe optional. Now, we need a Director to construct our compiler, so let’s do that: 37

EXAMPLE public class Director { public void construct. Scheme. To. X 64 Compiler(Compiler. Builder

EXAMPLE public class Director { public void construct. Scheme. To. X 64 Compiler(Compiler. Builder builder) { builder. set. Source. Language(Language. SCHEME); builder. set. Target. Language(Language. ASSEMBLY); builder. set. PCLibrary(PCLibrary. MAYER); } public void construct. TSto. JSCompiler(Compiler. Builder builder) { builder. set. Source. Language(Language. TYPESCRIPT); builder. set. Target. Language(Language. JAVASCRIPT); builder. set. PCLibrary(PCLibrary. PARSEC); } } 38

EXAMPLE Finally, this is how we would use our Director and builders: public static

EXAMPLE Finally, this is how we would use our Director and builders: public static void main(String[] args) { Director director = new Director(); OCaml. Compiler. Builder builder = new OCaml. Compiler. Builder(); director. construct. Scheme. To. X 64 Compiler(builder); OCaml. Compiler compiler = builder. build(); compiler. compile("(+ 1 2)"); } 39

THE PATTERN IN THE WILD If you’re using Java or C#, you may have

THE PATTERN IN THE WILD If you’re using Java or C#, you may have stumbled upon a very famous builder: the String. Builder class. It doesn’t have a Director, but it follows the pattern of constructing a String part-by-part (using. append), and then calling to. String() to build the entire String. 40

WHEN TO USE? . . . the Builder pattern is a good choice when

WHEN TO USE? . . . the Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters. (Joshua Bloch, “Effective Java”, 3 rd Edition, 2018) 41

Prototype Pattern 42

Prototype Pattern 42

INTENT Specify the kinds of objects to create using a prototypical instance, and create

INTENT Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype. 43

PATTERN DIAGRAM 44

PATTERN DIAGRAM 44

PARTICIPANTS • Prototype – declares an interface for cloning itself. • Concrete. Prototype –

PARTICIPANTS • Prototype – declares an interface for cloning itself. • Concrete. Prototype – implements an operation for cloning itself. 45

EXAMPLE We are building a web scraper object, but alas, network calls are slow.

EXAMPLE We are building a web scraper object, but alas, network calls are slow. We would like to make the request to a specified URL only once, and then have multiple objects with the same content so we can work on them independently. 46

EXAMPLE Since we are using Java, we already have an interface to fulfill the

EXAMPLE Since we are using Java, we already have an interface to fulfill the Prototype role, and that is the Cloneable interface, which exposes a single method: clone(). 47

EXAMPLE Our Concrete. Prototype will be our Web. Scraper class: 48

EXAMPLE Our Concrete. Prototype will be our Web. Scraper class: 48

EXAMPLE public class Web. Scraper implements Cloneable { private String. Buffer content; public Web.

EXAMPLE public class Web. Scraper implements Cloneable { private String. Buffer content; public Web. Scraper(String url) { try { Http. Client client = Http. Client. new. Http. Client(); Http. Request request = Http. Request. new. Builder(URI. create(url)). build(); Http. Response<String> response = client. send( request, Body. Handlers. of. String()); content = new String. Buffer(response. body()); } catch (IOException | Interrupted. Exception e) {. . . } }. . . } 49

EXAMPLE public class Web. Scraper implements Cloneable {. . . @Override protected Object clone()

EXAMPLE public class Web. Scraper implements Cloneable {. . . @Override protected Object clone() { Web. Scraper result; try { result = (Web. Scraper) super. clone(); result. content = new String. Buffer(result. content); } catch (Clone. Not. Supported. Exception e) { result = null; } return result; } public String get. Content() { return content; } } 50

EXAMPLE public static void main(String[] args) { System. out. println("Creating instance. . . ");

EXAMPLE public static void main(String[] args) { System. out. println("Creating instance. . . "); Web. Scraper scraper 1 = new Web. Scraper("https: //www. cs. bgu. ac. il"); System. out. println("Content: " + scraper 1. get. Content()); System. out. println("Cloning. . . "); Web. Scraper scraper 2 = (Web. Scraper) scraper 1. clone(); scraper 2. get. Content(). insert(1, "/"); System. out. println("Content: " + scraper 1. get. Content()); System. out. println("Content: " + scraper 2. get. Content()); } 51

SHALLOW VS. DEEP CLONE In the example, Java’s native clone() wasn’t enough, because it

SHALLOW VS. DEEP CLONE In the example, Java’s native clone() wasn’t enough, because it does a shallow copy. We wanted to clone the String. Buffer too, so we made a new String. Buffer for the clone. When writing deep clones, watch out for circular references! 52

USING A PROTOTYPE REGISTRY The Prototype pattern is often used with a registry: a

USING A PROTOTYPE REGISTRY The Prototype pattern is often used with a registry: a mapping between a key and a Concrete. Prototype. If there are only a few prototypes we need in our program, we create them all at startup, and only clone (instead of creating instances using new). 53

FACTORY METHOD VS. PROTOTYPE So when do we use a factory method? When do

FACTORY METHOD VS. PROTOTYPE So when do we use a factory method? When do we use a prototype? A factory will always create “fresh” instances, meaning it will use new to create them, thus incurring instantiation costs. We avoid that by using a prototype. 54

FACTORY METHOD VS. PROTOTYPE However, it’s important to remember that a prototype will return

FACTORY METHOD VS. PROTOTYPE However, it’s important to remember that a prototype will return a clone of itself. So if you don’t need the exact same state in your object, you’re better off using a factory. 55