设计模式

创建型模式

创建型模式是涉及如果创建程序中的对象的设计模式

单例模式(Singleton)

当你有一个全局的对象需要在系统的不同地方使用,例如一个数据库,一些操作都是在该数据库上进行。因为数据库带有全局的属性,所以你只想要该数据库的一个实例。

总结来说,当出现以下情况时,你可以考虑使用单例模式:

  1. 一个类只需要一个实例,但不清楚系统的那一部分应该拥有或者管理该实例时。
  2. 你希望该实例在代码中随处可用。
  3. 实例仅在第一次使用时才进行初始化(延迟话初始)

例如,数据库只需要连接一次,当在其他地方需要使用该数据库连接实例时,直接返回该数据库实例就好。

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
import java.util.Objects;

public final class Database {
private static Database database;

private Database() {}

public static Database getInstance() {
if (database == null) {
database = new Database();
database.connect("/usr/local/data/users.db");
}
return database;
}

// Connects to the remote database.
private void connect(String url) {
Objects.requireNonNull(url);
}

public static void main(String[] args) {
Database a = Database.getInstance();
Database b = Database.getInstance();
// 这里使用“==”时确认a和b指向的是同一个database的实例对象。
System.out.println(a == b);
}
}

工厂模式

工厂是任何创建对象的东西,工厂对于向调用者隐藏构造细节非常有用。

  • 如果常见对象的东西是方法,那么它被称为工厂方法。
  • 如果创建对象的东西也是一个对象,那么它被称为抽象工厂。当想要把对象的构造分离到完全独立的java接口中时,这非常有用。

抽象工厂模式 (Abstract Factory)

何时使用抽象工厂

  • 你想对调用者隐藏详细信息
  • 你希望将多个相关对象的构造封装到单个Java接口中。

例如下面的例子:
我们有一个页面解析器工厂接口,他有一个get方法是返回对参数url特定的页面解析器。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.udacity.webcrawler.parser;

/**
* A factory interface that supplies instances of {@link PageParser} that have common parameters
* (such as the timeout and ignored words) preset from injected values.
*/
public interface PageParserFactory {

/**
* Returns a {@link PageParser} that parses the given {@link url}.
*/
PageParser get(String url);
}

接下来,我们用一个页面解析器工厂实现类来实现这个接口

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
package com.udacity.webcrawler.parser;

import com.udacity.webcrawler.Timeout;
import com.udacity.webcrawler.profiler.Profiler;

import javax.inject.Inject;
import java.time.Duration;
import java.util.List;
import java.util.regex.Pattern;

/**
* A {@link PageParserFactory} that wraps its returned instances using a {@link Profiler}.
*/
final class PageParserFactoryImpl implements PageParserFactory {
private final Profiler profiler;
private final List<Pattern> ignoredWords;
private final Duration timeout;

@Inject
PageParserFactoryImpl(
Profiler profiler, @IgnoredWords List<Pattern> ignoredWords, @Timeout Duration timeout) {
this.profiler = profiler;
this.ignoredWords = ignoredWords;
this.timeout = timeout;
}

@Override
public PageParser get(String url) {
// Here, parse the page with the initial timeout (instead of just the time remaining), to make
// the download less likely to fail. Deadline enforcement should happen at a higher level.
PageParser delegate = new PageParserImpl(url, timeout, ignoredWords);
return profiler.wrap(PageParser.class, delegate);
}
}

当我们给不同的url构造页面解析器时,只有url是不同的,timeoutingoredWords 等参数是相同的。然后,我们重写了get方法,将urltimeoutignoredWords等参数传入页面构造器实现类来实例化一个页面构造器。这样,我们不用重复指定传入哪些相同的参数(timeoutignoredWords),而只需要传入不同的url,通过多次调用get方法就可以得到不同的页面解析器实例。

在调用者的眼里,调用的方式是如下的:

1
2
3
4
5
PageParserFactory f = new PageParserFactory(
Duration.ofSecond(5), List.of("foo"));
)
PageParser p1 = factory.get("udacity.com");
PageParser p2 = factory.get("wikipedia.com");

建造者模式(Builder)

建造者是一个可变的工厂,逐个属性构造要创建的对象的状态,然后构建该对象。类似前面构建页面解析器的过程,我们这次使用建造者模式来得到不同的页面解析器。如下:
我们在PageParser类中创建一个Builder内部类,Builder中属性字段和PageParser中的相同,并且实现了每个属性的set方法。同时实现一个返回页面构造器实例的build方法。

build方法内部的实现其实就是调用PageParser的构造函数来返回一个页面构造器的实例

这样在客户端,我们就可以通过Builder 来构造页面解析器

行为模式 (Behavioral Pattern)

行为模式关注对象间的责任分配,主要处理对象之间的通信。

策略模式 (Strategy Pattern)

定义一个接口来表示某一种任务或者问题。每个具体实现都定义了解决任务的不同策略。例子如下:

首先创建一个策略接口来表示某一种任务:

1
2
3
public interface Strategy {
public int doOperation(int num1, int num2);
}

接着创建实现接口的实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}


public class OperationSubtract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}


public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}

接着创建一个Context类,通过context实例来改变策略,从而改变策略的具体行为:

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
public class Context {
private Strategy strategy;

public Context(Strategy strategy){
this.strategy = strategy;
}

public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}



//客户端调用
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}

依赖注入(Dependency Injection )

什么是依赖

依赖项是指代码运行所需要的任何内容。当我们谈论依赖注入时,依赖项通常指您的代码导入、创建或使用的对象、类或接口。

什么是依赖注入

依赖注入是一种设计模式,它将依赖项的创建移至代码外部。你不需要创建对象,而是通过依赖注入框架帮你创建对象,然后将这些对象注入到类中。