2. “In software engineering, a software design
pattern is a general reusable solution to a
commonly occurring problem within a given
context in software design. It is not a finished
design that can be transformed directly into
source or machine code. It is a description or
template for how to solve a problem that can be
used in many different situations.”
6. The Beginner uses patterns everywhere
As learning progresses, the Intermediate mind
starts to see where patterns are needed and
where they aren’t.
7. The Beginner uses patterns everywhere
As learning progresses, the Intermediate mind
starts to see where patterns are needed and
where they aren’t.
The Zen mind is able to see patterns
where they fit naturally.
8. Scope Type of Pattern
Creational Structural Behavioral
Class-Level Factory Method Adapter
Interpreter
Template Method
Object-Level
Abstract Factory
Builder
Prototype
Singleton
Bridge
Composite
Decorate
Facade
Proxy
Flyweight
Chain of Responsibility
Command
Iterator
Mediator
Memento
Observer
State
Strategy
Visitor
How relationships between
classes and defined via
inheritance.
Established at compile
time.
How relationships between
objects and defined via
composition.
Established at runtime time.
How classes and objects
interact and distribute
responsibility. 封裝⾏行行為
Let you compose classes
or objects into larger
structures.
Involve object instantiation
and provide a way to
decouple a client from the
object it needs instantiate
Classification
9. Principles behind the design patterns
• Program to an interface, not an implementation (trying whenever
is possible to do not declare variables to be instances of particular
concrete class. i.e., programming to a super type)
• Favor object composition (aggregation) over class inheritance
(inheritance breaks encapsulation and can not be defined
dynamically at run time. i.e., change behavior at runtime.
• Delegation (each request to an object called is forwarded to
another object)
11. Abstract Factory Pattern
• Intent: provide an interface for creating families of related or dependent
objects without specifying their concrete classes.
• Example:
• User interfaces for different operating system.
• Select device drivers according to machine capacity.
• 從資料庫抓出 Shape,顯⽰示並且印出這些形狀狀
• 有兩兩種 display resolution, print resolution
12. Different Drivers for Different Machines
• 所以考慮有兩兩種 Family,低解析度與⾼高解析度。
• A Low-resolution (Low-Capacity) family: LRDD and LRPD
• A High-resolution (High-Capacity) family: HRDD and HRPD
For Driver In a Low-Capacity Machine, Use In a High-Capacity Machine, Use
Display
LRDD
Low-resolution display driver
HRDD
High-resolution display driver
Print
LRPD
Low-resolution print driver
HRPD
High-resolution print driver
13. Alternative 1: Switch
class ApControl { ...
public void doDraw() {
switch (RESOLUTION) {
case LOW:
// use lrdd
case HIGH:
// use hrdd
}
}
public void doPrint() {
switch (RESOLUTION) {
case LOW:
// use lrpd
case HIGH:
// use hrpd
}
}
}
14. Alternative 1: Switch
• Tight Coupling: 如果要增加⼀一個新的
resolution,要同時改兩兩個地⽅方。
• Weak Cohesion: 有兩兩個不相關的任務
doDraw 與 doPrint。它們都必須建立
形狀狀,也必須操⼼心使⽤用哪種 Driver。
class ApControl { ...
public void doDraw() {
switch (RESOLUTION) {
case LOW:
// use lrdd
case HIGH:
// use hrdd
}
}
public void doPrint() {
switch (RESOLUTION) {
case LOW:
// use lrpd
case HIGH:
// use hrpd
}
}
}
15. Switches may indicate a need for
abstraction
使⽤用 Switch 時,⾃自⼰己要提醒⾃自⼰己:
• 可能需要使⽤用 polymorphism
• 可能錯放 responsibilities
• switch case 不總是惡惡魔,但⼀一次要同時改多處的 switch 是有點危險
的設計。
18. Alternative 3: Replace Switches with
abstraction (封裝變化)
{{abstract}}
DisplayDriver
-
LRDD
-
HRDD
-
{{abstract}}
PrintDriver
-
LRPD
-
HRPD
-
class ApControl { ...
public void doDraw() {
switch (RESOLUTION) {
case LOW:
// use lrdd
case HIGH:
// use hrdd
}
}
public void doPrint() {
switch (RESOLUTION) {
case LOW:
// use lrpd
case HIGH:
// use hrpd
}
}
}
20. We Got a Problem
• How do we create the appropriate object?
• 讓 ApControl 處理理
• 將來來會有維護上的困難,需要⼀一直修改。
• 違反 SRP。只管怎麼⽤用就好
• 讓專⾨門的⼯工廠來來處理理 - 專⾨門負責挑選適合的 Driver => ResFactory
21. Responsibilities and Cohesion
• 從 ApControl ⾓角度來來看,knowing how to work with the appropriate
objects
• 從 Factory ⾓角度來來看,keeping track of which drivers to use, i.e.,
decide which objects are appropriate.
• Factory 負責產⽣生(初始化)適合的 Driver
• ApControl 負責使⽤用它們
22.
23. 避免 Switch 的⽅方法之⼀一
{{abstract}}
ResolutionFactory
+ GetDispDriver()
+ GetPrintDriver()
LowResFact
-
HighResFact
-
abstract class ResFactory {
abstract public DisplayDriver getDispDrvr();
abstract public PrintDriver getPrtDrvr();
}
class LowResFact : ResFactory {
public DisplayDriver getDispDrvr() {
return new LRDD();
}
public PrintDriver getPrtDrvr() {
return new LRPD();
}
}
class HighResFact : ResFactory {
public DisplayDriver getDispDrvr() {
return new HRDD();
}
public PrintDriver getPrtDrvr() {
return new HRPD();
}
}
注意:
多個 method
24.
25. Key Features
Intent You want to have families or sets of objects for particular clients (or cases).
Problem Families of related objets need to be instantiated.
Solution
Coordinates the creation of families of objects. Gives a way to take the rules of how to perform the
instantiation out of the client object that is using theses created objects.
Participants and
Collaborators
The AbstractFactory defines the interface for how to create each member of the family of objects
required. Typically, each family is created by having its own unique ConcreteFactory.
Consequences The pattern isolates the rules of which objects to use from the logic of how to use theses objects.
Implementation
Define an abstract class that specifies which objects are to be made. Then implement one concrete
class for each family. Tables or files can also be used to accomplish the same thing.
configuration or lookup table
27. C# Example
public class DatabaseAgentFactory : IAgentFactory
{
public IAgentA CreateAgentA()
{
return new AgentA_Database();
}
public IAgentB CreateAgentB()
{
return new AgentB_Database();
}
}
public class XMLAgentFactory : IAgentFactory
{
public IAgentA CreateAgentA()
{
return new AgentA_Xml();
}
public IAgentB CreateAgentB()
{
return new AgentB_Xml();
}
}
public interface IAgentFactory
{
IAgentA CreateAgentA();
IAgentB CreateAgentB();
}
public class AgentB_Database : IAgentB
{
internal AgentB_Database()
{ /* Construction here */}
// IAgentB method implementations
}
public class AgentA_Database : IAgentA
{
internal AgentA_Database()
{ /* Construction here */}
// IAgentA method implementations
}
public class AgentB_Xml : IAgentB
{
internal AgentB_Xml()
{ /* Construction here */}
// IAgentB method implementations
}
public class AgentA_Xml : IAgentA
{
internal AgentA_Xml()
{ /* Construction here */}
// IAgentA method implementations
}
public interface IAgentB
{
// Add some methods here!
}
public interface IAgentA
{
// Add some methods here!
}
28. Check List
• Map out a matrix of "platforms" versus "products".
• Define a factory interface that consists of a factory method per
product. (abstract factory 是由 factory method 組成的)
• Define a factory derived class for each platform that encapsulates
all references to the new operator.
• The client should retire all references to new, and use the factory
methods to create the product objects. (⽤用⼯工廠取代 new,封裝
instantiation)
30. Key Features
Intent
Define an interface for creating an object, but let subclasses decide which class to instantiate. Defer
instantiation to subclasses.
Problem
A class needs to instantiate a derivation of another class, but doesn’t know which one (不知道具體的產
品是哪⼀一種). Factory Method allows a derived class to make the decision.
Solution A derived class makes the decision on which (product) class to instantiate and how to instantiate it.
Participants and
Collaborators
AbstractProduct is the interface for the type of object that the Factory Method creates.
AbstractCreator is the interface that defines the Factory Method.
Consequences Clients will need to subclass the Creator class to make a particular ConcreteProduct.
Implementation
Use a method in the abstract class that is abstract. The abstract class’ code to this method when it
needs to instantiate a contained object but does not know which particular object it needs.
建議參參考之前⼩小學堂投影片
56. Key Features
Intent
You want to have only one of an object, but there is no global object that controls the instantiation of this
object. You also want to ensure that all entities are using the same instance of this object, without
passing a reference to all of them.
Problem
Several different client objects need to refer to the same thing, and you want to ensure that you do not
have more than one of them.
Solution Guarantees one instance. Make the class itself responsible for keeping track of its sole instance. [GoF]
Participants and
Collaborators
Clients create an instance of the Singleton solely through the getInstance method.
Consequences
Clients need not concern themselves whether an instance of the Singleton exists. This can be
controlled from within the Singleton. "Just-in-time initialization" or "initialization on first use"
57. Implementation
• Declare the instance as a private static data member.
• Provide a public static member function that encapsulate all
initialization code (like a static factory).
• Provides access to the instance, i.e., getInstance method.
1. Constructor 為 private,⽤用⼾戶無法透過 new 去 instantiate 它
2. 提供⼀一個⾃自⾝身的靜態私有變量量,⽤用來來保存唯⼀一實體。
3. 提供⼀一個公有的靜態⼯工廠⽅方法
60. C# Implementation
using System;
public class Singleton
{
private static Singleton instance;
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
61. C# Implementation
using System;
public class Singleton
{
private static Singleton instance;
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
⽋欠考慮的實作
無法確保唯⼀一性
62. C# Implementation with Lock
using System;
public class Singleton
{
private static Singleton instance;
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
lock
63. C# Implementation with Lock
using System;
public class Singleton
{
private static Singleton instance;
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
暴暴⼒力力的鎖
形成效能的瓶頸
lock
64. Double Checked Lock
if check() { // lock hint
lock() {
if check() {
// perform your lock-safe code here
}
}
}
The idea behind this pattern is that you would want to check first, to minimize any
aggressive locking, since an IF statement is less expensive than the locking.
Secondly we would want to wait and acquire the exclusive lock so only one execution is
inside that block at a single time. But between the first check and the acquisition of the
exclusive lock there could have been another thread that did acquire the lock, therefore we
would need to check again inside the lock to avoid replacing the instance with another one.
http://marcio.io/2015/07/singleton-pattern-in-go/
65. Double-Check Locking Implementation
using System;
public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
lock
check
check
66. .NET 4.0+ Implementation
public class Singleton
{
private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance
{
get
{
return lazy.Value;
}
}
private Singleton()
{
}
}
http://csharpindepth.com/Articles/General/Singleton.aspx
Lazy<T> lambda expression
無引數
67. Check List
• Define a private static attribute in the "single instance" class.
• Define a public accessor function in the class.
• Do "Lazy initialization" (creation on first use) in the accessor
function.
• Define all constructors to be protected or private.
• Clients may only use the accessor functions to manipulate the
Singleton (usually static functions).
68. Common Use
• ⼤大部分時間幾乎不⽤用!!!
• 要提供⼀一個唯⼀一的、公開的資源存取點。例例如 GPIO 或者 Printer
Task Queue.
• Abstract Factory, Builder and Prototype 通常⽤用 Singleton 實作
• Facade 與 State object 通常是 Singleton
78. Key Features
Intent
Manage the reuse of objects when it is either expensive to create an object to there is a limit on the
number objects of a particular type that can be created.
Problem
The creation and/or management of objects must follow a well-defined set of rules. Typically these
rules relate to how to make an object, how many objects can be created and how to reuse existing
objects once they have finished their current tasks.
Solution
The Client calls ReusablePool’s acquireReusable method when it needs a Reusable object. If the
pool is empty, then acquireReusable method creates a Reusable object if it can; otherwise, it waits
until a Reusable object is returned to the collection.
Participants and
Collaborators
The ReusablePool manages the availability of Reusable objects for use by the Client. Client then
uses instances of reusable objects for a limited amount of time. ReusablePool contains all the
Reusable objects so that they can be managed in a unified way.
Consequences
Works best when the demand for objects is fairly consistent over time; large variations in demand
can lead to performance problems. To address thus issue in the Object Pool pattern, limit the
number of objects that can be created. Keeping the logic to manage the creation of instances
separate from the class whose instances are being managed results in a more cohesive design.
80. Anti-Pattern: Object Cesspool
• If objects returning to the pool are not validated, the pool can
gradually fill with garbage objects, turning it into a cesspool of
unusable objects.
• How to identify:
• Object pool does not have a “release object” method.
• Overly simple “release object” method, which does not check
object validity.
Object Cesspool Anti-Pattern
81. Example
public class ConfigWriterPool {
private Queue<ConfigWriter> pool = new Queue<ConfigWriter>();
public ConfigWriter Get() {
if (pool.Count > 0) {
return pool.Dequeue();
} else {
return BuildNewConfigWriter();
}
}
public void Release(ConfigWriter writer) {
pool.Queue(writer);
}
}
without check
82. Example
public class ConfigWriterPool {
private Queue<ConfigWriter> pool = new Queue<ConfigWriter>();
public ConfigWriter Get() {
if (pool.Count > 0) {
return pool.Dequeue();
} else {
return BuildNewConfigWriter();
}
}
public void Release(ConfigWriter writer) {
pool.Queue(writer);
}
}
without check
public class ConfigWriterPool {
private Queue<ConfigWriter> pool = new Queue<ConfigWriter>();
public ConfigWriter Get() {
if (pool.Count > 0) {
return pool.Dequeue();
} else {
return BuildNewConfigWriter();
}
}
public void Release(ConfigWriter writer) {
if (IsReadyForUse(writer))
pool.Queue(writer);
else
DestroyWriter(writer);
}
private bool IsReadyForUse(ConfigWriter writer) {
/* check that the writer is not null, open, in the correct
position, etc. */
}
private void DestroyWriter(ConfigWriter writer) {
/* cleanup and dispose of the writer, it will not be used again */
}
}
drop or drop then create
83. Check List
• Create ObjectPool class with private array of Objects inside
• Create acquire and release methods in ObjectPool class
• Prevent Object Cesspool
• Make sure that your ObjectPool is Singleton
90. Key Features
Intent
Separate the construction of a complex object from its representation so that the same construction
process can create different representations. Parse a complex representation, create one of several
targets.
Problem
An application needs to create the elements of a complex aggregate. The specification for the
aggregate exists on secondary storage and one of many representations needs to be built in primary
storage.
Solution
The director encapsulates the parsing of the common input. The Builder hierarchy makes possible the
polymorphic creation of many peculiar representations or targets.
Participants and
Collaborators
Builder:抽象建造者
ConcreteBuilder:具體建造者
Director:指揮者
Product:產品
103. Check List
• Decide if a common input and many possible representations (or
outputs) is the problem at hand.
• Encapsulate the parsing of the common input in a Reader class.
• Define a Builder derived class for each target representation.
• The client creates a Reader(Director) object and a Builder object, and
registers the latter with the former.
• The client asks the Reader(Director) to "construct".
• The client asks the Builder to return the result.
106. You are Pokenmon Designer
• 你正在開發 Pokemon Go
• 每種怪的產⽣生 (instantiate) 需要不少 composition,步驟複雜⼜又耗時
• 當訓練師走到特定地點時,我們希望能快速產⽣生不同等級的 波波
• 可預期這些波波只有等級不同,其他能⼒力力都完全⼀一樣
107. You are Pokenmon Designer
• 你正在開發 Pokemon Go
• 每種怪的產⽣生 (instantiate) 需要不少 composition,步驟複雜⼜又耗時
• 當訓練師走到特定地點時,我們希望能快速產⽣生不同等級的 波波
• 可預期這些波波只有等級不同,其他能⼒力力都完全⼀一樣
108.
109. Key Features
Intent
Specifying the kind of objects to create using a prototypical instance, creating new objects by copying
this prototype.
Problem Application "hard wires" the class of object to create in each "new" expression.
Participants and
Collaborators
Define an interface in Abstract prototype for cloning itself. Concrete prototype implements an
operation for cloning itself. Client creates a new object by asking a prototype to clone itself.
Consequences
It hides the concrete product classes from the client, thereby reducing the number of names clients
know about. Moreover, these patterns let a client work with application-specific classes without
modification.
Implementation
Declare an abstract base class that specifies a pure virtual "clone" method, and, maintains a dictionary
of all "cloneable" concrete derived classes. Any class that needs a "polymorphic constructor" capability:
derives itself from the abstract base class, registers its prototypical instance, and implements the clone()
operation.
112. C# Example
public class PrototypeRegistry
{
Hashtable catalog = new Hashtable();
public LoadProtos()
{
catalog.Add("1", new ConcretePrototype1());
catalog.Add("2", new ConcretePrototype2());
}
public Prototype GetProto(string s)
{
if catalog.ContainsKey(s)
{
return hashtable[s].Clone();
}
return null;
}
}
public abstract class Prototype
{
public abstract Prototype Clone();
}
public class ConcretePrototype1 : Prototype
{
public override Prototype Clone()
{
// Clones the concrete class.
return (Prototype)this.MemberwiseClone();
}
}
public class ConcretePrototype2 : Prototype
{
public override Prototype Clone()
{
// Clones the concrete class.
return (Prototype)this.MemberwiseClone();
}
}
class MainApp
{
static void Main()
{
registry = new PrototypeRegistry();
registry.LoadProtos();
Prototype[] protos = new Prototype[2];
protos[0] = registry.GetProto("1").clone();
protos[1] = registry.GetProto("2").clone();
foreach (Prototype pro in protos)
{
Product product = creator.FactoryMethod();
Console.WriteLine("Created {0}", pro.GetType().Name);
}
// Wait for user
Console.ReadKey();
}
}
113. Check List
• Add a clone() method to the existing "product" hierarchy.
• Design a registry that maintains a cache of prototypical objects. The
registry could be encapsulated in a new Factory class, or in the base
class of the "product" hierarchy.
• Design a factory method that: may (or may not) accept arguments, finds
the correct prototype object, calls clone() on that object, and returns the
result.
• The client replaces all references to the new operator with calls to the
factory method.
114. 討論
• Prototype pattern 與 Factory pattern 接收到 User 命令之後即時⽣生產
物件的⽅方式不同,在 Prototype pattern中,我們需要先打造出物件的
原型樣版,待鑄模程序完成以後,接下來來就能夠輕易易地以此原型複製
產⽣生出全新的物件。
• Prototypes are useful when object initialization is expensive
• 注意 Shallow Copy 與 Deep Copy 問題,Clone 有時候很難實作。
117. Take away
• 了了解 Pattern 適⽤用的 Context
• 不要死背 Pattern,思考背後的原則,也能得到類似的結構
• Program to an interface, not an implementation
• Favor object composition (aggregation) over class inheritance
• Delegation
• SOLID
• Pattern 不能硬套,要視情況微調 (KISS)
• 試著⾃自⼰己畫出 Sequence Diagram,還有⽤用⾃自⼰己熟悉的語⾔言實作看看,配合 unit test 與 CI 練習
118. Reference
• Design Patterns: Elements of Reusable Object-Oriented Software. GoF
• Design Patterns Explained, 2nd. Alan Shalloway et al.
• Head First Design Patterns. Eric Freeman et al.
• SourceMaking
• 圖說設計模式
• ⼤大話設計模式. 程傑