Home design patterns
Post
Cancel

design patterns

References

Gang of Four Design Patters

Open Closed Principle

As applications evolve, changes are required. Changes are required when new functionality is added or existing functionality is updated in the application. Often in both situations, you need to modify the existing code, and that carries the risk of breaking the application’s functionality. For good application design and the code writing part, you should avoid change in the existing code when requirements change. Instead, you should extend the existing functionality by adding new code to meet the new requirements. You can achieve this by following the Open Closed Principle.

Open for extension

This means that the behavior of a software module, say a class can be extended to make it behave in new and different ways. It is important to note here that the term “extended ” is not limited to inheritance using the Java extend keyword. As mentioned earlier, Java did not exist at that time. What it means here is that a module should provide extension points to alter its behavior. One way is to make use of polymorphism to invoke extended behaviors of an object at run time.

Closed for modification

This means that the source code of such a module remains unchanged.

Single Responsibility Principle

In object-oriented programming (Java, among other languages, follows this paradigm), you will often hear terms such as robustness, cohesion, coupling etc. Cohesion is a way to measure how much the code segments within one module (methods of a class, classes inside a package…) belong together. The higher the cohesion – the better, since high cohesion implies easier maintenance and debugging, greater code functionality and reusability. The term cohesion is sometimes contrasted with the concept of coupling, and often, loose coupling of modules is related to high cohesion.

Another widely used term is robustness, which could be defined as the ability of a computer system or algorithm to handle mistakes and malfunctions (which could be caused by various factors such as programmer’s mistake or incorrectly formatted user input). A robust system is one that can handle these unwanted situations elegantly. There are various ways for a software engineer to achieve robustness, such as testing the code for different kinds of inputs, but generally, in order to achieve robustness (and high cohesion), programmers follow a certain set of rules and principles for better organization of object-oriented programs. One such principle is the single responsibility principle.

The single responsibility principle revolves around the claim that a certain code module (most often, a class) should only have responsibility over one part of the functionality provided by the software. In software engineering books, this is sometimes also defined like this: the module should only have one reason to change. This means that a division of concerns is performed in the program, and the methods for every concern should be completely encapsulated by a single class. Now it is obvious that this approach contributes to the high cohesion – since methods related to the same concern (same part of the functionality) will be members of the same class, and robustness – since this reduces the possibility of error. Furthermore, if an error does occur, the programmer will be more likely to find the cause, and finally, solve the problem.

Interface Segregation Principle

What the Interface Segregation Principle says is that your interface should not be bloated with methods that implementing classes don’t require. For such interfaces, also called “fat interfaces”, implementing classes are unnecessarily forced to provide implementations (dummy/empty) even for those methods that they don’t need. In addition, the implementing classes are subject to change when the interface changes. An addition of a method or change to a method signature requires modifying all the implementation classes even if some of them don’t use the method.

The Interface Segregation Principle advocates segregating a “fat interface” into smaller and highly cohesive interfaces, known as “role interfaces”. Each “role interface” declares one or more methods for specific behavior. Thus clients, instead of implementing a “fat interface”, can implement only those “role interfaces” whose methods are relevant to them.

Strategy (策略)

example

發射子彈的方法采用不同的策略:如向四個方向發射

Factory (工廠)

Factory Method (工廠方法)

Creates objects without specifying the exact class to create.

Allows the creation of objects without specifying their concrete type.

包裝了製造物體(或產品)的方法

AbstractFactory生产Object

CarFactory生產Car

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// abstract product
public abstract class Pizza {
    public abstract void addIngredients();

    public void bakePizza() {
        System.out.println("Pizza baked at 400 for 20 minutes.");
    }
}

// concrete CheesePizza
public class CheesePizza extends Pizza {

    @Override
    public void addIngredients() {
        System.out.println("Preparing ingredients for cheese pizza.");
    }
}

// concrete PepperoniPizza
public class PepperoniPizza extends Pizza {
    @Override
    public void addIngredients() {
        System.out.println("Preparing ingredients for pepperoni pizza.");
    }
}

// concrete VeggiePizza
public class VeggiePizza extends Pizza {
    @Override
    public void addIngredients() {
        System.out.println("Preparing ingredients for veggie pizza.");
    }
}

// abstract factory
public abstract class BasePizzaFactory {
    // 工厂方法
    public abstract Pizza createPizza(String type);
}


// concrete factory
public class PizzaFactory extends BasePizzaFactory{
    @Override
    public  Pizza createPizza(String type){
        Pizza pizza;
        switch (type.toLowerCase())
        {
            case "cheese":
                pizza = new CheesePizza();
                break;
            case "pepperoni":
                pizza = new PepperoniPizza();
                break;
            case "veggie":
                pizza = new VeggiePizza();
                break;
            default: throw new IllegalArgumentException("No such pizza.");
        }
        pizza.addIngredients();
        pizza.bakePizza();
        return pizza;
    }
}
1
2
3
4
5
6
7
8
9
10
11
import org.junit.Test;
public class PizzaFactoryTest {
    @Test
    public void testMakePizzas(){
        // we have not created any concrete Pizza objects in the code. Therefore, if we enhance the Pizza implementation classes in a newer release, we do not require updating any client or test code. We can safely rely on the Creator to provide us the enhanced object.
        BasePizzaFactory pizzaFactory = new PizzaFactory();
        // 调用工厂方法去创建实例,而不需要指定或写死某个concrete product class
        Pizza cheesePizza = pizzaFactory.createPizza("cheese");
        Pizza veggiePizza = pizzaFactory.createPizza("veggie");
    }
}

Abstract Factory (抽象工廠)

we have not created any concrete Pizza objects in the code. Therefore, if we enhance the Pizza implementation classes in a newer release, we do not require updating any client or test code. We can safely rely on the Creator to provide us the enhanced object.

From implementation point of view, the key difference between the factory method and abstract factory patterns is that factory method is just a method to create objects of a single type, while abstract factory is an object to create families of objects.

生產一簇商品:weapon, food

魔法世界的人:MagicStick, sunshine

火星人: …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package guru.springframework.gof.abstractFactory.topping;
public interface Cheese {
     void prepareCheese();
}

public class GoatCheese implements Cheese {
    public  GoatCheese(){
       prepareCheese();
    }
    @Override
    public void prepareCheese(){
        System.out.println("Preparing goat cheese...");
    }
}

public class MozzarellaCheese implements Cheese{
    public  MozzarellaCheese(){prepareCheese();
    }
    @Override
    public void prepareCheese() {
        System.out.println("Preparing mozzarella cheese...");
    }
}

public interface Sauce {
     void prepareSauce();
}
public class TomatoSauce implements Sauce {
    public TomatoSauce(){
        prepareSauce();
    }
     @Override
    public void prepareSauce() {
        System.out.println("Preparing tomato sauce..");
    }
}

public class CaliforniaOilSauce implements Sauce {
    public CaliforniaOilSauce(){
        prepareSauce();
    }
    @Override
    public void prepareSauce() {
        System.out.println("Preparing california oil sauce..");
    }
}

ToppingFactory thar create source and cheese family

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package guru.springframework.gof.abstractFactory;
import guru.springframework.gof.abstractFactory.topping.Cheese;
import guru.springframework.gof.abstractFactory.topping.Sauce;
// abstract toppingFactory
public abstract class BaseToppingFactory {
    public abstract Cheese createCheese();
    public abstract Sauce createSauce();
}

import guru.springframework.gof.abstractFactory.topping.MozzarellaCheese;
import guru.springframework.gof.abstractFactory.topping.TomatoSauce;
// concrete SicilianToppingFactory
public class SicilianToppingFactory extends BaseToppingFactory{
    @Override
    public  Cheese createCheese(){return new MozzarellaCheese();}
    @Override
    public  Sauce createSauce(){return new TomatoSauce();}
}


import guru.springframework.gof.abstractFactory.topping.CaliforniaOilSauce;
import guru.springframework.gof.abstractFactory.topping.GoatCheese;
// concrete GourmetToppingFactory
public class GourmetToppingFactory extends BaseToppingFactory{
    @Override
    public Cheese createCheese(){return new GoatCheese();}
    @Override
    public Sauce createSauce(){return new CaliforniaOilSauce();}
}

create pizza with toppingFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package guru.springframework.gof.abstractFactory.product;
import guru.springframework.gof.abstractFactory.BaseToppingFactory;
public class CheesePizza extends Pizza {
    BaseToppingFactory toppingFactory;
    public CheesePizza(BaseToppingFactory toppingFactory){
        this.toppingFactory=toppingFactory;
    }
    @Override
    public void addIngredients() {
        System.out.println("Preparing ingredients for cheese pizza.");
        toppingFactory.createCheese();
        toppingFactory.createSauce();
            }
}

public class PepperoniPizza extends Pizza {
    BaseToppingFactory toppingFactory;
    public PepperoniPizza(BaseToppingFactory toppingFactory)
    {
        this.toppingFactory=toppingFactory;
    }
    @Override
    public void addIngredients() {
        System.out.println("Preparing ingredients for pepperoni pizza.");
        toppingFactory.createCheese();
        toppingFactory.createSauce();
    }
}

public class VeggiePizza extends Pizza {
    BaseToppingFactory toppingFactory;
    public VeggiePizza(BaseToppingFactory toppingFactory)
    {
        this.toppingFactory=toppingFactory;
    }
    @Override
    public void addIngredients() {
        System.out.println("Preparing ingredients for veggie pizza.");
        toppingFactory.createCheese();
        toppingFactory.createSauce();
    }
}

PizzaFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import guru.springframework.gof.abstractFactory.product.CheesePizza;
import guru.springframework.gof.abstractFactory.product.PepperoniPizza;
import guru.springframework.gof.abstractFactory.product.Pizza;
import guru.springframework.gof.abstractFactory.product.VeggiePizza;
// concrete SicilianPizzaFactory
public class SicilianPizzaFactory extends BasePizzaFactory {
    @Override
    public  Pizza createPizza(String type){
        Pizza pizza;
       BaseToppingFactory toppingFactory= new SicilianToppingFactory();
        switch (type.toLowerCase())
        {
            case "cheese":
                pizza = new CheesePizza(toppingFactory);
                break;
            case "pepperoni":
                pizza = new PepperoniPizza(toppingFactory);
                break;
            case "veggie":
                pizza = new VeggiePizza(toppingFactory);
                break;
            default: throw new IllegalArgumentException("No such pizza.");
        }
        pizza.addIngredients();
        pizza.bakePizza();
        return pizza;
    }
}

// concrete GourmetPizzaFactory
public class GourmetPizzaFactory extends BasePizzaFactory {
    @Override
    public  Pizza createPizza(String type){
        Pizza pizza;
        BaseToppingFactory toppingFactory= new GourmetToppingFactory();
        switch (type.toLowerCase())
        {
            case "cheese":
                pizza = new CheesePizza(toppingFactory);
                break;
            case "pepperoni":
                pizza = new PepperoniPizza(toppingFactory);
                break;
            case "veggie":
                pizza = new VeggiePizza(toppingFactory);
                break;
            default: throw new IllegalArgumentException("No such pizza.");
        }
        pizza.addIngredients();
        pizza.bakePizza();
        return pizza;
    }
}

Testing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package guru.springframework.gof.abstractFactory;
import guru.springframework.gof.abstractFactory.product.Pizza;
import org.junit.Test;
public class GourmetPizzaFactoryTest {
    @Test
    public void testCreatePizza() throws Exception {
    BasePizzaFactory pizzaFactory=new GourmetPizzaFactory();
        Pizza cheesePizza= pizzaFactory.createPizza("cheese");
        Pizza veggiePizza=pizzaFactory.createPizza("veggie");
    }
    
        @Test
    public void testCreatePizza2() throws Exception {
        BasePizzaFactory pizzaFactory=new SicilianPizzaFactory();
        Pizza cheesePizza=pizzaFactory.createPizza("cheese");
        Pizza pepperoniPizza =pizzaFactory.createPizza("pepperoni");
    }
}

Spring Bean (Bean工厂)

属于抽象工厂

Facade(門面)

example

GameModel對於TankFrame

ViewModel對於Controller

Mediator (調停者)

example

GameModel通過gameObjects對象存儲和管理所有游戲實體

Decorator (裝飾器)

While thinking about extending functionality, the first thing which is likely to occur to a new programmer is inheritance. However, inheritance may not be the ideal solution in all situations. When you inherit functionality through subclassing, the functionality is statically set at compile time and it doesn’t always lead to the most flexible nor maintainable designs. If you need to add new features, code modifications are required, which is a violation of the Open Closed Principle.

Instead, you can attach new responsibility to an object dynamically. This is exactly the intended use of the decorator pattern.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
    String description;
    public String getDescription() {
        return description;
    }
    public abstract double cost();
}

public class RoseBouquet extends FlowerBouquet{
    public RoseBouquet(){
        description = "Rose bouquet";
    }
    public  double cost(){
        return 12.0;
    }
}

public class OrchidBouquet extends FlowerBouquet{
    public OrchidBouquet(){
        description = "Orchid bouquet";
    }
    public  double cost(){
        return 29.0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package guru.springframework.gof.decorator.decorators;
import guru.springframework.gof.decorator.components.FlowerBouquet;
public abstract class FlowerBouquetDecorator extends FlowerBouquet {
    // 重新生命為抽象方法
    public abstract String getDescription();
}

public class Glitter extends FlowerBouquetDecorator{
    FlowerBouquet flowerBouquet;
    public Glitter(FlowerBouquet flowerBouquet){
        this.flowerBouquet=flowerBouquet;
    }
    public  String getDescription(){
        return flowerBouquet.getDescription()+", glitter";
    }
    public double cost()
    {
        return 4+flowerBouquet.cost();
    }
}

public class PaperWrapper extends FlowerBouquetDecorator{
    FlowerBouquet flowerBouquet;
    public PaperWrapper(FlowerBouquet flowerBouquet){
        this.flowerBouquet=flowerBouquet;
    }
    public  String getDescription(){
        return flowerBouquet.getDescription()+", paper wrap";
    }
    public double cost()
    {
        return 3+flowerBouquet.cost();
    }
}

public class RibbonBow extends FlowerBouquetDecorator{
    FlowerBouquet flowerBouquet;
    public RibbonBow(FlowerBouquet flowerBouquet){
        this.flowerBouquet=flowerBouquet;
    }
    public  String getDescription(){
        return flowerBouquet.getDescription()+", ribbon bow";
    }
    public double cost()
    {
        return 6.5+flowerBouquet.cost();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package guru.springframework.gof.decorator.components;
import guru.springframework.gof.decorator.decorators.Glitter;
import guru.springframework.gof.decorator.decorators.PaperWrapper;
import guru.springframework.gof.decorator.decorators.RibbonBow;
import org.junit.Test;
public class FlowerBouquetTest {
    @Test
    public void testFlowerBouquet() {
        /*Rose bouquet with no decoration*/
        FlowerBouquet roseBouquet = new RoseBouquet();
        System.out.println(roseBouquet.getDescription()
                + " $ " + roseBouquet.cost());
         /*Rose bouquet with paper wrapper, ribbon bow, and glitter*/
        FlowerBouquet decoratedRoseBouquet = new RoseBouquet();
        decoratedRoseBouquet=new PaperWrapper(decoratedRoseBouquet);
        decoratedRoseBouquet=new RibbonBow(decoratedRoseBouquet);
        decoratedRoseBouquet=new Glitter(decoratedRoseBouquet);
        System.out.println(decoratedRoseBouquet.getDescription()
                + " $ " + decoratedRoseBouquet.cost());
         /*Orchid bouquet with double paper wrapper and ribbon bow*/
        FlowerBouquet decoratedOrchidBouquet = new OrchidBouquet();
        decoratedOrchidBouquet=new PaperWrapper(decoratedOrchidBouquet);
        decoratedOrchidBouquet=new PaperWrapper(decoratedOrchidBouquet);
        decoratedOrchidBouquet=new RibbonBow(decoratedOrchidBouquet);
        System.out.println(decoratedOrchidBouquet.getDescription()
                + " $ " + decoratedOrchidBouquet.cost());
    }
}

example

GameObjectDecorator聚合了gameObject

Decorators本身也是gameObject,因此Decorator也可以聚合其他Decorator

TailDecoratorRectDecorator

Chain of Responder (責任鏈)

You can relate the Chain of Responsibility pattern with a Customer Service technical help desk that you call up with a technical query/help for some product or service (think yourself as a request object). A technical help desk executive tries to resolve it (Think in terms of objects – the first object in the chain). If they can’t resolve it – maybe for some billing related issues, it moves to a billing help desk executive (the second object). If the billing help desk can’t resolve either, your request goes to the general help desk (the third object), and so on – until someone handles your request.

example

物體碰撞檢測

Collider封裝了檢測方法

Collider Chain本身也是Collider

BulletWallCollider、TankWallCollider、BulletTankCollider

Adapter

We use adapters in our daily lives. The moment you plug in your mobile handset or your laptop to a socket for charging, an adapter is at work. What the adapter does is makes the socket that produces 120 V (or 220 V for European standard) and the mobile device that requires 4 V work together. Similarly, by using the adapter pattern in the programming world, you can make incompatible interfaces work together.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package guru.springframework.gof.adapter.source;
public interface TextFormattable {
    String formatText(String text);
}

public class NewLineFormatter implements TextFormattable{
    @Override
    public String formatText(String text)
    {
        String formattedText=text.replace(".","\n");
        return formattedText;
    }
}


package guru.springframework.gof.adapter.adaptee;
public interface CsvFormattable {
    String formatCsvText(String text);
}


public class CsvFormatter implements CsvFormattable{
    @Override
    public String formatCsvText(String text){
        String formattedText=text.replace(".",",");
        return formattedText;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package guru.springframework.gof.adapter.csvadapter;
import guru.springframework.gof.adapter.adaptee.CsvFormattable;
import guru.springframework.gof.adapter.source.TextFormattable;
// The adapter
public class CsvAdapterImpl implements TextFormattable {
    CsvFormattable csvFormatter;
    public CsvAdapterImpl(CsvFormattable csvFormatter){
        this.csvFormatter=csvFormatter;
    }
    @Override
    public String formatText(String text)
    {
        String formattedText=csvFormatter.formatCsvText(text);
        return formattedText;
    }
}

Composite Pattern

As a programmer you will deal with hierarchical trees of objects at some point or other. Hierarchical tree structures can come in different flavors, and one can be a tree of components (think as objects) that can be either leaf or node. A leaf is an object that doesn’t have children, while a node does. A node can have one or more leaves or other nodes. This is called recursive composition and can be best illustrated through a file system directory structure.

example: Product Catalog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package guru.springframework.gof.composite;
public abstract class CatalogComponent {
    public void add(CatalogComponent catalogComponent){
        throw new UnsupportedOperationException("Cannot add item to catalog.");
    }
    public void remove(CatalogComponent catalogComponent){
        throw new UnsupportedOperationException("Cannot remove item from catalog.");
    }
     public String getName(){
        throw new UnsupportedOperationException("Cannot return name.");
    }
    public double getPrice(){
        throw new UnsupportedOperationException("Cannot return price.");
    }
    public void print(){
        throw new UnsupportedOperationException("Cannot print.");
    }
}



public class Product extends CatalogComponent{
    private String name;
    private double price;
    public Product (String name, double price){
        this.name=name;
        this.price=price;
    }
    @Override
    public String getName() {
        return  this.name;
    }
    @Override
    public double getPrice() {
        return this.price;
    }
    @Override
    public void print(){
        System.out.println("Product name: "+name+" Price: "+price);
    }
}

import java.util.ArrayList;
public class ProductCatalog extends CatalogComponent{
    private ArrayList<CatalogComponent> items=new ArrayList<>();
    private String name;
    public ProductCatalog(String name){
        this.name=name;
    }
    @Override
    public String getName() {
        return name;
    }
    @Override
    public void print(){
       for(CatalogComponent comp : items)
        {
            comp.print();
        }
    }
    @Override
    public void add(CatalogComponent catalogComponent){
        items.add(catalogComponent);
    }
    @Override
    public void remove(CatalogComponent catalogComponent){
        items.remove(catalogComponent);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package guru.springframework.gof.composite;
import org.junit.Test;
import static org.junit.Assert.*;
public class CatalogComponentTest {
    @Test
    public void testPrint() throws Exception {
        /*Create primary products for main catalog*/
        CatalogComponent mJeanProduct=new Product("M: Jeans 32", 65.00);
        CatalogComponent mTShirtProduct=new Product("M: T Shirt 38", 45.00);
        /*Create a composite product catalog and add female products to it*/
        CatalogComponent newCatalog = new ProductCatalog("Female Products");
        CatalogComponent fJeans=new Product("F: Jeans 32", 65.00);
        CatalogComponent fTShirts=new Product("F: T Shirt 38", 45.00);
        newCatalog.add(fJeans);
        newCatalog.add(fTShirts);
       /*Create a composite product catalog and add kid products to it*/
        CatalogComponent kidCatalog = new ProductCatalog("Kids Products");
        CatalogComponent kidShorts=new Product("Return Gift", 23.00);
        CatalogComponent kidPlayGears = new Product("Summer Play Gear", 65.00);
        kidCatalog.add(kidShorts);
        kidCatalog.add(kidPlayGears);
        /*Create primary catalog and add primary products and new catalogs to it*/
        CatalogComponent mainCatalog=new ProductCatalog("Primary Catalog");
        mainCatalog.add(mJeanProduct);
        mainCatalog.add(mTShirtProduct);
        mainCatalog.add(newCatalog);
        mainCatalog.add(kidCatalog);
        /*Print out product/catalog information*/
        mainCatalog.print();
    }
}
This post is licensed under CC BY 4.0 by the author.