高级软件设计2024 期末复习

写在前面

本篇复习参考了多个来源,因此可能会在一些细节上出现相互矛盾的地方,这时候需要多多思考和理解。

策略模式 Strategy Pattern

image-20241227154520757

image-20241223153121545

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

image-20241227154630105

观察者模式 Observer Pattern

image-20241224151011204

观察者模式定义了对象之间的一对多依赖,这样依赖,当一个对象改变状态时,它的所有依赖着都会收到通知并自动更新。

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
71
72
73
public interface Subject{
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}

public interface Observer {
public void update(float temperature, float humidity, float pressure);
}

public class WeatherData implements Subject{
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;

public WeatherData() {
observers = new ArrayList<>();
}

public void registerObserver(Observer o){
observers.add(o);
}

public void removeObserver(Observer o){
int i = observers.indexOf(o);
if (i>=0)
observers.remove(i);
}

public void notifyObservers(){
for (Observer o:observers){
o.update(temp,humidity,pressure);
}
}

public void measurementsChanged(){ // 当从气象站得到更新时,通知观察者
notifyObservers();
}

public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}


public interface DisplayElement{
public void display();
}

public class CurrentConditionsDisplay implements Observer, DisplayElement{
private float temperature;
private float humidity;
private Subject weatherData;

public CurrentConditionsDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

public void update(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
display();
}

public void display(){
System.out.println("Current condition: " + temperature + "F degrees and " + humidity + "%humidity");
}
}

image-20241227155358090

装饰者模式 Decorator Pattern

image-20241224140904660

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

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
public abstract class Beverage { 
String description = "Unknown Beverage";

public String getDescription(){
return description;
}

public abstract double cost();
}

public abstract class CondimentDecorator extends Beverage { //抽象装饰者
public abstract String getDescription(); // 具体装饰者类需重新实现getDescription方法
}

public class Espresso extends Beverage{
public Espresso(){
description = "Espresso";
}

public double cost(){
return 0.99;
}
}

public class Milk extends CondimentDecorator{ //具体装饰者
Beverage beverage;

public Milk(Beverage beverage){
this.beverage = beverage;
}

public getDescription(){
return beverage.getDescription() + ", Milk";
}

public double cost(){
return beverage.cost() + 0.1;
}
}

JAVA I/O类

image-20241224142208550

编写一个装饰者类,将字母转为小写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LowerCaseInputStream extends FilterInputStream{
public LowerCaseInputStream(InputSteam in){
super(in);
}

public int read() throws IOException{
int c = super.read();
return c==-1? c: Character.toLowerCase((char)c);
}

public int read(bytes[] b, int offset, int len) throws IOException{
int result = super.read(b, offset, len);
for (int i = offset; i<offset+result; i++){
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}

public class Client {
public static void main(String[] args){
InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
}
}

某系统提供一个数据加密功能,可以对字符串进行加密。最简单的加密算法通过对字母进行移位来实现,同时还提供稍复杂的逆向输出加密,还提供更为高级的求模加密。用户首先使用最简单的加密算法对字符串进行加密,如果觉得还不够可以对加密后的结果使用其他的加密算法进行二次加密,当然也可以进行第三次加密。使用Decrator模式来设计。

img
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
public abstract class EncryptComponent {
abstract String encrypt(String str);
}

public class OriginalEncrypt extends EncryptComponent {
@Override
String encrypt(String str) {
System.out.println("对字符串 \'"+str+"\' 使用原始加密 =====> 原始加密结果");
String encryptStr = "原始加密结果";
return encryptStr;
}
}

public class EncryptDecorator extends EncryptComponent {
EncryptComponent encryptComponent;

public EncryptDecorator(EncryptComponent encryptComponent) {
this.encryptComponent = encryptComponent;
}

@Override
String encrypt(String str) {
return encryptComponent.encrypt(str);
}
}

public class OtherAEncrypt extends EncryptDecorator {
public OtherAEncrypt(EncryptComponent encryptComponent) {
super(encryptComponent);
}

@Override
String encrypt(String str) {
return otherAEncrypt(super.encrypt(str));
}

public String otherAEncrypt(String str){
System.out.println("对字符串 \'"+str+"\' 使用OtherA加密 =====> OtherA加密结果");
return "OtherA 加密结果";
}
}

public class Client {

public static void main(String[] args) {
EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;
String result;
originalEncrypt = new OriginalEncrypt();
result = originalEncrypt.encrypt("初始数据");
System.out.println("-----------------------------------------------");

otherAEncrypt = new OtherAEncrypt(originalEncrypt);
result = otherAEncrypt.encrypt("初始数据");
System.out.println("-----------------------------------------------");

otherBEncrypt = new OtherBEncrypt(originalEncrypt);
result = otherBEncrypt.encrypt("初始数据");
System.out.println("-----------------------------------------------");

otherBEncrypt = new OtherBEncrypt(otherAEncrypt);
result = otherBEncrypt.encrypt("初始数据");
System.out.println("-----------------------------------------------");
}
}

image-20241227155641606

工厂模式 Factory Pattern

简单工厂模式 Simple Factory

image-20241224160308994

简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变。例如增加子类那么所有的客户类都要进行修改。

简单工厂其实并不是一个设计模式,反而比较像一个编程习惯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SimpleFactory {
public Product createProduct(int type){
if (type==1)
return new ConcreteProduct1();
else if (type==2)
return new ConcreteProduct2();
else
return new ConcreteProduct3();
}
}

public class Client {
public static void main(String[] args){
SimpleFactory simpleFactory = new SimpleFactory();
Product product = simpleFactory.createProduct(1);
}
}

工厂方法模式 Factory Method

image-20241227155930652

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类。

在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Factory{
abstract public Product factoryMethod();
public void doSomething(){
Product product = factoryMethod();
// do something with the product
}
}

public class ConcreteFactory1 extends Factory{
public Product factoryMethod(){
return new ConcreteProduct1();
}
}

public class ConcreteFactory2 extends Factory{
public Product factoryMethod(){
return new ConcreteProduct2();
}
}

工厂方法的优缺点

工厂方法模式(Factory Method)是一种常用的设计模式,它在创建对象时提供了更多的灵活性和扩展性。了解其优缺点有助于更好地决定何时使用这种模式。

优点

  1. 分离职责:工厂方法模式将对象的创建和使用分离,降低了类之间的耦合度。这种分离使得客户端代码从具体类的创建中解耦,提高了系统的灵活性和可维护性。
  2. 符合开闭原则:在引入新的产品类时,不需要修改已有代码,只需添加相应的工厂类。这符合开闭原则,即对扩展开放,对修改关闭。
  3. 扩展性:可以很容易地引入新的产品类型,并用新的具体工厂来创建这些产品,使得系统更易于扩展。
  4. 隐藏创建逻辑:工厂方法隐藏了对象创建的细节,客户端不需要知道具体实现,只需关心所需产品的接口。

缺点

  1. 类的数量增多:每添加一个新的产品,就需要添加一个具体的工厂类,这会导致类的数量成倍增加,增加了系统的复杂度。
  2. 增加了系统的抽象性:引入工厂类也增加了系统的抽象性和理解难度。
  3. 代码复用性降低:由于每个工厂只创建一种产品,这可能会导致工厂之间的代码重复。

image-20241227160020421

抽象工厂模式 Abstract Factory

image-20241224162737150

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

image-20241227160125249

三个工厂模式之间的关系

  1. 递进关系:简单工厂模式是最简单的形式,工厂方法模式在此基础上增加了更多的抽象和灵活性,而抽象工厂模式是在工厂方法的基础上进一步扩展,用于处理产品族的创建。
  2. 灵活性和复杂性:简单工厂模式最简单,但灵活性较差;工厂方法模式灵活性较高,但实现相对复杂;抽象工厂模式灵活性最高,但实现最为复杂。

单例模式 Singleton Pattern

image-20241224164043228

单例模式确定一个类只有一个实例,并提供一个全局访问点。

懒汉式——线程不安全

uniqueInstance可能被多次实例化。

1
2
3
4
5
6
7
8
9
10
11
public class Singleton{
private static Singleton uniqueInstance;

private Singleton(){}

public static Singleton getUniqueInstance(){
if (uniqueInstance == null)
uniqueInstance = new Singleton();
return uniqueInstance;
}
}

直接实例化——线程安全

丢失了延迟实例化带来的节约资源的好处。

1
2
3
4
5
private static Singleton uniqueInstance = new Singleton();

public static Singleton getUniqueInstance(){
return uniqueInstance;
}

懒汉式——线程安全

uniqueInstance只会被实例化一次,但即使uniqueInstance已经被实例化了,线程被阻塞仍然需要等待,影响性能。

1
public static synchronized Singleton getUniqueInstance(){...}

双重检查锁——线程安全

双重检验锁先判断uniqueInstance是否已经被实例化,如果没有实例化,才对实例化语句进行加锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton{
private volatile static Singleton uniqueInstance; // 注意 volatile

private Singleton(){}

public static Singleton getUniqueInstance(){
if (uniqueInstance == null){ // 双重校验锁
synchronized (Singleton.class){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

关于使用volatile关键字:

image-20241224165506265

image-20250102111011608

命令模式 Command Pattern

image-20250102132813243

命令模式将“请求”封装成对象,以便使用不同的请求、队列或日志来参数化其他对象。命令模式也支持可撤销的操作。

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public interface Command {
void execute();
void undo();
}

// 开灯命令
public class LightOnCommand implements Command{
Light light;

public LightOnCommand(Light light){
this.light = light;
}

public void execute(){
light.on();
}

public void undo(){
light.off(); //撤销操作与execute相反
}
}

// 关灯命令
public class LightOffCommand implements Command{
Light light;

public LightOffCommand(Light light){
this.light = light;
}

public void execute(){
light.off();
}

public void undo(){
light.on();
}
}

// 默认命令,这是一个空对象
public class NoCommand implements Command{
public void execute(){
//do nothing
}

public void undo(){}
}

public class Light{
public void on(){
System.out.println("Light is on!");
}

public void off(){
System.out.println("Light is off!");
}
}

// 遥控器
public class Invoker {
private Command[] onCommands;
private Command[] offCommands;
private Command undoCommand; // 记录前一个命令,用于撤销
private final int slotNum = 7

public Invoker() {
this.onCommands = new Command[slotNum];
this.offCommands = new Command[slotNum];

Command noCommand = new NoCommand();
for (int i=0;i<slotNum;i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}

// 设置命令
public void setCommand(int slot, Command onCommand, Command offCommand){
onCommands[slot] = onCommand;
offCommands[slot] = offcommand;
}

public void onButtonWasPushed(int slot){
onCommands[slot].execute();
undoCommand = onCommands[slot];
}

public void offButtonWasPushed(int slot){
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
}

宏命令 Macro Command

宏命令是一种特殊的命令,它可以执行一系列的命令。简单来说,宏命令是命令的集合,当执行宏命令时,它内部的所有命令依序执行。

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
// 命令接口
public interface Command {
void execute();
}

// 宏命令类
public class MacroCommand implements Command {
// 存储命令对象的列表
private List<Command> commands = new ArrayList<>();

// 添加命令
public void addCommand(Command command) {
commands.add(command);
}

// 删除命令
public void removeCommand(Command command) {
commands.remove(command);
}

// 执行宏命令,即依次执行列表中的所有命令
@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}
}

// 测试类
public class TestMacroCommand {
public static void main(String[] args) {
MacroCommand macro = new MacroCommand();

// 添加具体命令
macro.addCommand(new ConcreteCommandA());
macro.addCommand(new ConcreteCommandB());

// 执行宏命令
macro.execute();
}
}

image-20250102111104869

适配器模式 Adapter Pattern

适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以无间合作。

对象适配器

image-20241224173526153

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 interface Duck{
public void quark();
public void fly();
}

public interface Turkey {
public void gobble(); //火鸡只会gobble
public void fly(); //火鸡飞不远
}

//对象适配器
public class TurkeyAdapter implements Duck{
Turkey turkey;

public TurkeyAdapter(Turkey turkey){
this.turkey = turkey;
}

public void quack(){
turkey.gobble();
}

public void fly(){
for (int i=0;i<5;i++)
turkey.fly();
}
}

类适配器

image-20241224174128646

注意:Java无法实现多继承

双向适配器

在软件设计中,双向适配器模式是适配器模式的一个变体,它允许两个不兼容的接口之间进行双向通信。在标准的适配器模式中,适配器通常使得一个类看起来像另一个类,而在双向适配器模式中,适配器同时兼容两个不同的接口。

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
// 第一个接口
interface InterfaceA {
void methodA();
}

// 第二个接口
interface InterfaceB {
void methodB();
}

// 实现第一个接口的类
class ClassA implements InterfaceA {
@Override
public void methodA() {
System.out.println("ClassA methodA()");
}
}

// 实现第二个接口的类
class ClassB implements InterfaceB {
@Override
public void methodB() {
System.out.println("ClassB methodB()");
}
}

// 双向适配器
class TwoWayAdapter implements InterfaceA, InterfaceB {
private InterfaceA classA;
private InterfaceB classB;

public TwoWayAdapter(InterfaceA classA, InterfaceB classB) {
this.classA = classA;
this.classB = classB;
}

@Override
public void methodA() {
classB.methodB();
}

@Override
public void methodB() {
classA.methodA();
}
}

// 测试类
public class TwoWayAdapterDemo {
public static void main(String[] args) {
ClassA classA = new ClassA();
ClassB classB = new ClassB();
TwoWayAdapter adapter = new TwoWayAdapter(classA, classB);

adapter.methodA(); // 调用ClassB的methodB()
adapter.methodB(); // 调用ClassA的methodA()
}
}

image-20250102111350165

外观模式 Facade Pattern

image-20241225002249848

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

某系统需要提供一个文件加密模块,加密流程包括:读源文件、加密、保存加密文件。读取文件和保存文件使用流来实现,三个业务相对独立,封装在不同的类中;现在需要提供一个统一的加密外观类,用户可以直接使用该加密外观类完成文件的读取、加密和保存三个操作。使用Facade模式来设计。

img
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class readfile
public void read(){}


public class codefile{
public void code(){}
}

public class savefile{
public void save(){}
}

public class codeFacade{
private readfile rf = readfile();
private codefile cf = codefile();
private savefile sf = savefile():
public void jiami(){
rf.read();
cf.code();
sf.save():
}
}

image-20250102111519102

迪米特法则/最少知识原则

——一个对象应当对其他对象有尽可能少的了解。

  • 这个原则鼓励对象之间的松耦合,这样可以降低系统的复杂性,提高模块的独立性。
  • 在实际编程中,这意味着一个对象不应该调用另一个对象的方法来获取数据,而是应该通过自己的方法来获取。
  • 这个原则有助于降低类之间的依赖性,从而使系统更容易维护和扩展。

模板方法模式 Template Method Pattern

image-20241225002938681

模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class AbstractClass {
final void templateMethod(){ //模板方法为final,不希望子类覆写
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hook();
}

abstract void primitiveOperation1(); //子类需要实现这两个抽象方法

abstract void primitiveOperation2();

final void concreteOperation(){ //具体的方法为final
// 这里是实现
}

void hook(){} //勾子函数,子类可看情况进行覆盖
}

image-20250102111826034

好莱坞原则

——别调用我们,我们会调用你。

  • 它用于控制何时以及如何对象间的交互发生。
  • 在这个模式下,高层次的组件告诉低层次的组件“如果有什么重要的事情发生,我会通知你”,而不是低层次组件向高层次组件发起通讯。
  • 这个原则常用于框架设计,其中框架控制流程,而用户自定义的部分则等待被框架调用。
  • 这样可以提供一个明确的协议来决定何时以及如何用户的代码被执行。

迭代器模式 Iterator Pattern

image-20241225005843802

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

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

public interface Aggregate{
Iterator createIterator();
}

public class ConcreteAggregate{
private Item[] items;

public Iterator createIterator(){
return new ConcreteIterator(items);
}
}

public class ConcreteIterator<Item> implements Iterator{
private Item[] items;
private int position = 0;

public ConcreteIterator(Item items){
this.items = items;
}

public Object next(){
return items[position++];
}

public boolean hashNext(){
return position < items.length;
}
}

public class Client {
public static void main(String[] args){
Aggregate aggregate = new ConcreteAggregate();
Iterator<Integer> iterator = aggregate.createIterator();
...
}
}

image-20250102112235113

组合模式 Composite Pattern

组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

透明组合

所有组件(叶子节点和组合节点)共享一个统一的接口;接口中定义了组合节点特有的操作(如 addremove 方法),叶子节点也会继承这些操作

pros:

客户端代码对组合结构一视同仁(透明);

不需要区分叶子节点和组合节点,简化了客户端逻辑。

cons:

叶子节点不需要也不能实现组合特有的操作(如 add remove),但仍被迫继承这些方法

对于叶子节点,调用这些方法可能会抛出异常或什么都不做,违反了接口隔离原则(ISP)。

image-20241225011432769
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
public abstract class Component{ //牺牲单一职责原则换取透明性,这样就不用考虑是叶子还是组合节点
protected String name;

public Component(String name){
this.name = name;
}

public void print(){
print(0);
}

abstract void print(int level);

abstract public void add(Componet componet);

abstract public void remove(Component componet);
}

public class Composite extends Componet{
private List<Component> childs;

public Composite(String name){
super(name);
childs = new ArrayList();
}

void print(int level) {
for (int i=0;i<level;i++)
System.out.println("-");
System.out.println("Composite: " + name);
for (Componet component:childs)
component.print(level+1);
}

public void add(Component component){
child.add(component);
}

public void remove(Component component){
child.remove(component);
}
}

public class Leaf extends Componet {
public Leaf(String name){
super(name);
}

void print(int level){
for (int i=0;i<level;i++)
System.out.println("-");
System.out.println("Leaf: " + name);
}

public void add(Component component){
throw new UnsupportedOpeartionException();
}

public void remove(Component component){
throw new UnsupportedOpeartionException();
}
}

安全组合

将叶子节点和组合节点的接口分开:组件基类只定义通用操作(如 operation),组合特有的操作(如 add 和 remove)仅在组合节点中定义;叶子节点不会暴露组合特有的操作。

pros:

更符合接口隔离原则(ISP),叶子节点只包含必要的操作。

避免客户端调用叶子节点上不支持的方法。

cons:

客户端需要区分叶子节点和组合节点,可能需要额外的类型检查(如通过 dynamic_cast 或其他机制)。

杀毒软件(AntiVirus)既能对系统中不同类型的文件 TextFile、ImageFile、VideoFile杀毒,也能对文件夹的杀毒,对文件夹杀毒就是对其中所包含文件的杀毒。使用Composite模式来设计。

image-20241225193013767

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
public abstract class MyElement{
public abstract void killvirus();
}

public class TextFile extends MyElement{
public void killvirus(){
System.out.println("对text文件进行杀毒");
}
}

public class ImageFile extends MyElement{
public void killvirus(){
System.out.println("对image文件进行杀毒");
}
}

public class VideoFile extends MyElement{
public void killvirus(){
System.out.println("对video文件进行杀毒");
}
}

public class File extends MyElement{
private ArrayList list = new ArrayList();
public void add(MyElement element){
list.add(element);
}
public void remove(MyElement element){
list.remove(element);
}
public void killvirus(){
for(Object object:list){
((MyElement).object).killvirus();
}
}
}

image-20250102112609925

状态模式 State Pattern

image-20250102113219739

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

image-20241225180740505
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public interface State{
//投入25分钱
void insertQuarter();

//退回25分钱
void ejectQuarter();

//转动曲柄
void turnCrank();

//发放糖果
void dispense();
}

public class HasQuarterState implements State {
private GumballMachine gumballMachine;

public HasQuarterState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("You can't insert another quarter");
}

public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(guamballMachine.getNoQuarterState());
}

public void turnCrank() {
System.out.println("You turned...");
gumballMachine.setState(guamballMachine.getSoldState());
}

public void dispense() {
System.out.println("No gumball dispensed");
}
}

...

public class GumballMachine{
private State soldOutState;
private State noQuarterState;
private State hashQuarterState;
private State soldState;

private State state;
private int count = 0;

public GumballMachine(int numberGumballs){
count = numberGumballs;
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this)

if (numberGumballs > 0){
state = noQuarterState;
} else {
state = soldOutState;
}
}

public void insertQuarter(){
state.insertQuarter();
}

public void ejectQuarter(){
state.ejectQuarter();
}

public void turnCrank(){
state.turnCrank();
state.dispense();
}

public void setState(State){
this.state = state;
}

public void releaseBall(){
System.out.println("A gumball comes rolling out the slot...");
if (count != 0)
count = -1;
}

public State get...State(){
return ...State;
}

public int getCount(){
return count;
}
}

image-20250102113108767

代理模式 Proxy Pattern

image-20250102122941388

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

使用代理模式创建代表,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或者需要安全控制的对象。

远程代理

image-20241226120307380

Java RMI

虚拟代理

image-20241226120242531

以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。

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
public interface Image {
void showImage();
}

public class HighResolutionImage implements Image{
private URL imageURL;
private long startTime;
private int height;
private int width;

public int getHeight(){
return height;
}

public int getWidth(){
return width;
}

public HighResolutionImage(URL imageURL){
this.imageURL = imageURL;
this.startTime = System.currentTimeMillis();
this.width = 600;
this.height = 600;
}

public boolean isLoad(){
// 模拟图片加载,延迟3s完成
long endTime = System.currentTimeMillis();
return endTime - startTime > 3000;
}

public void showImage(){
System.out.println("Real Image: " + imageURL);
}
}

public class ImageProxy implements Image{
private HighResolutionImage highResolutionImage;

public ImageProxy(HighResolutionImage highResolutionImage){
this.highResolutionImage = highResolutionImage;
}

public void showImage(){
while (!highResolutionImage.isLoad()){
try{
System.out.println("Temp Image: " + highResolutionImage.getWidth() + "-" + highResolutionImage.getHeight());
Thread.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
}
highResolutionImage.showImage();
}
}

public class Client{
public static void main(String[] args)){
String image = "http://image.jpg";
URL url = new URL(image);
HighResolutionImage highResolutionImage = new HighResolutionImage(url);
ImageProxy imageProxy = new ImageProxy(highResolutionImage);
imageProxy.showImage();
}
}

保护代理

某论坛已注册用户和游客的权限不同,已注册用户拥有发帖、修改自己的注册信息等功能;游客只能看别人的帖子,没有其他权限。使用Proxy模式来设计。

img
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
public interface AbstractPermission {
public void modifyUserInfo();
public void viewNote();
public void publishNote();
public void modifyNote();
public void setLevel(int level);
}

public class RealPermission implements AbstractPermission {
public void modifyUserInfo() {
System.out.println("修改用户信息");
}

public void viewNote() {

}

public void publishNote() {
System.out.println("发布新帖");
}

public void modifyNote() {
System.out.println("修改帖子");
}

public void setLevel(int level) {

}
}

public class PermissionProxy implements AbstractPermission {
private RealPermission permission = new RealPermission();
private int level = 0;

public void modifyUserInfo() {
if (0 == level) {
System.out.println("对不起,你没有权限");
}
else if (1 == level) {
permission.modifyUserInfo();
}
}

public void viewNote() {
System.out.println("查看帖子");
}

public void publishNote() {
if (0 == level) {
System.out.println("对不起,你没有权限");
}
else if (1 == level) {
permission.publishNote();
}
}

public void modifyNote() {
if (0 == level) {
System.out.println("对不起,你没有权限");
}
else if (1 == level) {
permission.modifyNote();
}
}

public void setLevel(int level) {
this.level = level;
}
}

image-20250102123020426

设计模式的七大原则

设计原则

标记 设计模式原则名称 英文 简单定义
OCP 开闭原则 Open Closed Principle 对扩展开放,对修改关闭
SRP 单一职责原则 Single Responsibility Principle 一个类只负责一个功能领域中的相应职责
LSP 里氏代换原则 Liskov Substitution Principle 所有引用基类的地方必须能透明地使用其子类的对象
DIP 依赖倒转原则 Dependency Inversion Principle 依赖于抽象,不能依赖于具体实现
ISP 接口隔离原则 Interface Segregation Principle 类之间的依赖关系应该建立在最小的接口上
CARP 合成/聚合复用原则 Composite/Aggregate Reuse Principle 尽量使用合成/聚合,而不是通过继承达到复用的目的
LOD 迪米特法则/最少知识原则 Law of Demeter / Least Knowledge Principle 一个软件实体应当尽可能少的与其他实体发生相互作用
好莱坞原则 Hollywood Principle 一种软件设计原则,其核心思想是 “Don’t call us, we’ll call you”,意思是低层组件不应该调用高层组件,而应该让高层组件来控制何时调用低层组件。这个原则用于减少系统中组件之间的依赖关系,提高模块的独立性和可重用性。

设计模式与设计原则

模式名称 相关的设计原则 缺点
策略模式 开闭原则、依赖倒转原则 客户端需要了解所有策略类的作用,增加使用复杂性
观察者模式 迪米特法则、依赖倒转原则 可能引起链式反应,增加系统复杂度
装饰模式 开闭原则、合成复用原则、单一职责原则 可能生成大量小对象,增加系统复杂性
工厂方法模式 依赖倒转原则、开闭原则 增加类的数量,系统复杂度增加
抽象工厂模式 依赖倒转原则、开闭原则 新增产品族时需要修改工厂接口,违反开闭原则
单例模式 依赖倒转原则 可能导致过多依赖,全局状态增加系统耦合度
命令模式 依赖倒转原则、开闭原则 增加类的数量,系统复杂度增加
适配器模式 合成复用原则 可能导致系统过于复杂,引入不必要的类
外观模式 迪米特法则 不能很好地限制客户端直接访问子系统
模板方法模式 开闭原则、里氏代换原则 子类实现复杂,导致系统可读性降低
迭代器模式 单一职责原则、开闭原则 增加了类的数量,可能导致系统复杂性增加
组合模式 合成复用原则、里氏代换原则 复杂性较高,调试和维护难度较大
状态模式 开闭原则、单一职责原则 状态类数量增加,导致系统复杂度上升
代理模式 迪米特法则、开闭原则 增加类的数量,系统复杂度增加

其他题目

设计模式与类库有何不同?

  1. 定义和目的:
  • 设计模式:设计模式是解决特定问题的通用、重复使用的解决方案框架。它们是在多个软件项目中经过验证的最佳实践,用于解决常见的设计问题或设计软件架构时的挑战
  • 类库:类库是一组封装了特定功能的类和接口的集合。这些库提供了实现特定任务(如图形处理、网络通信等)所需的具体实现,使开发人员可以避免从头开始编写代码。
  1. 抽象级别:
  • 设计模式工作在更高的抽象级别,它们提供了一种方法论,用于如何组织代码和模块以解决复杂问题。
  • 类库则是具体实现,提供了可以直接使用的代码,使得开发人员可以直接调用库中的类和方法来实现特定功能。
  1. 通用性和适用范围:
  • 设计模式具有更高的通用性,适用于多种编程语言和项目。例如,单例模式、观察者模式等在不同语言和项目中都有广泛应用。
  • 类库通常与特定的编程语言或平台绑定,例如Java的Spring框架,Python的NumPy库。
  1. 重用程度:
  • 设计模式重用的是思想和方法,而不是代码。它们需要开发人员根据具体项目的需求来实现。
  • 类库重用的是代码,开发人员可以通过调用库中的函数和对象来重用已实现的功能。

总的来说,设计模式是一种在软件架构层面上解决问题的方法,而类库提供了具体的工具和功能,用于实现这些设计模式或完成其他特定的编程任务。设计模式关注的是如何设计软件的结构和交互,而类库提供了完成这些设计所需的具体实现工具。

请解释设计模式如何对模型-视图-控制器(MVC)做出贡献?

Model-View-Controller(MVC)是一种常用的软件设计架构,用于实现用户界面。它将应用程序分为三个主要部分:

  1. 模型(Model):代表应用程序的核心功能和数据。它与数据存储直接交互,并包含与数据相关的逻辑。
  2. 视图(View):负责展示数据(模型)给用户。它是应用程序的用户界面。
  3. 控制器(Controller):作为模型和视图之间的中介,控制器接收用户的输入,并调用模型和视图去执行相应的操作。

设计模式如何促进MVC架构:

  1. 分离关注点(Separation of Concerns):设计模式鼓励将不同的功能分离开来。在MVC中,模型、视图和控制器各自管理不同的责任,这使得应用程序更容易维护和扩展。
  2. 观察者模式:这是MVC中经常使用的一个模式。在这个模式中,视图作为观察者,当模型的状态改变时,会通知视图,以便视图可以更新自己。这种模式促进了模型和视图之间的低耦合。
  3. 策略模式:视图和控制器实现了经典的策略模式:视图是一个对象,可以被调整使用不同的策略,而控制器提供了策略。视图只关心系统中可视的部分,对于任何界面行为,都委托给控制器处理。使用策略模式也可以让视图和模型之间的关系解耦,因为控制器负责和模型交互来传递用户的请求。对于工作是怎么完成的,视图毫不知情。
  4. 工厂模式:用于创建对象,特别是当有多种类型的对象需要被创建时。在MVC架构中,工厂模式可以用来创建控制器或视图,特别是在复杂的应用程序中。
  5. 适配器模式:这个模式用于确保接口的兼容性,特别是在集成第三方库或服务时。在MVC中,它可以帮助确保模型、视图和控制器的不同部分可以无缝协作。
  6. **组合模式:**显示包括了窗口、面板、按钮、文本标签等。每个显示组件如果不是组合节点(例如窗口),就是叶节点(例如按钮)。当控制器告诉视图更新时,只需告诉视图最顶层的组件即可,组合会处理其余的事。

请写出与工厂方法模式有关的OO原则(至少三个)

工厂方法模式是一种常用的设计模式,在面向对象编程中,它与多个面向对象设计原则紧密相关。以下是与工厂方法模式相关的至少三个面向对象原则:

  1. 单一职责原则(Single Responsibility Principle):这个原则指出一个类应该只有一个引起变化的原因。在工厂方法模式中,创建对象的职责被分配给专门的工厂类,这样做可以将对象的创建和使用分离,每个工厂类只负责创建一种类型的对象。这符合单一职责原则,因为工厂类只关心对象的创建过程,而不关心对象的具体使用。
  2. 开闭原则(Open-Closed Principle):这个原则表明软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。工厂方法模式允许系统在不修改现有代码的情况下引入新类型的对象,因为创建对象的代码被封装在工厂类中。当需要创建新类型的对象时,我们可以通过添加新的工厂类来扩展系统,而无需修改现有的工厂类或客户端代码。
  3. 依赖倒置原则(Dependency Inversion Principle):这个原则强调高层模块不应该依赖于低层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。在工厂方法模式中,客户端代码依赖于抽象的工厂接口,而不是具体的工厂实现类。这样,客户端代码就可以与具体的产品类解耦,只依赖于产品接口。当需要更换或添加新的产品时,只需提供相应的工厂实现,无需修改客户端代码。

这些原则共同构成了工厂方法模式的理论基础,帮助设计出灵活、可扩展且易于维护的系统。

什么是设计模式?什么是design pattern catalog?

设计模式是在软件工程中,针对特定问题的典型解决方案的一种标准化描述。它们是一套被广泛认可的最佳实践,可以用来解决在软件设计过程中经常出现的各种问题。设计模式不是可以直接转化成代码的模板,而是针对特定问题场景的一种指导方案。

设计模式通常包括以下几个方面:

  • 模式名称(Pattern Name):一个便于记忆的名字,可以简洁地描述模式的本质。
  • 问题(Problem):描述了应该在何种情况下使用设计模式。
  • 解决方案(Solution):描述了设计的元素、它们之间的关系及各自的职责和协作方式,但不具体到特定的实现。
  • 效果(Consequences):描述了模式应用的效果及使用模式可能带来的权衡。

而“Design Pattern Catalog”是指收集多种设计模式的目录。它不仅包括每种设计模式的详细描述,还通常会涉及实际应用的示例和考虑应用某个模式时的优缺点。这个目录可以帮助开发人员在特定的设计问题上找到合适的模式,并理解其应用的背景和影响。

设计模式通常被分为三大类:

  1. 创建型模式(Creational Patterns):这类模式与对象的创建有关,帮助使系统独立于如何创建、组合和表示对象。例如,单例模式、工厂方法模式、抽象工厂模式等。
  2. 结构型模式(Structural Patterns):这类模式处理对象的组合,通常用于建立对象间的关系,使它们更易于管理和维护。例如,适配器模式、装饰器模式、代理模式,外观模式(Facade Pattern)等
  3. 行为型模式(Behavioral Patterns):这类模式专注于对象间的责任分配。它们与对象之间的沟通、关系处理有关,有助于定义对象间的复杂控制流程。例如,观察者模式、策略模式、命令模式等。

这些模式提供了解决特定问题的框架,从而可以帮助开发者避免重复的工作,并提升代码的质量和可维护性。

请比较策略和状态模式。

策略模式和状态模式是设计模式中常见的两种模式,它们都属于行为型模式,但各自的应用场景和目的有所不同。

  1. 策略模式(Strategy Pattern)
  • 目的:允许在运行时选择算法或行为

  • 应用场景:

    • 多种算法或行为:当你有多种类似的算法或行为,它们可以互换使用时。例如,不同的排序算法或税率计算。
    • 算法独立于使用它们的客户端:策略模式允许在运行时切换算法,而不影响使用算法的代码。
    • 避免条件语句:如果你的代码有许多条件语句来决定执行哪个算法,策略模式可以帮助你去除这些条件语句。
    • 封装算法的变化:当算法的变化不应该影响到使用它们的代码时,使用策略模式可以封装这些变化。
  • 实现方式:定义一个策略接口,然后创建实现此接口的具体策略类。上下文类则使用策略接口作为字段,通过该字段调用具体策略的方法。

  • 优点:提高了算法的复用性和灵活性,可以独立于使用它们的客户端变化。

  • 缺点:客户端必须了解不同策略之间的差异,增加了客户端的复杂性。

  1. 状态模式(State Pattern)
  • 目的:允许对象在内部状态改变时改变它的行为。

  • 应用场景:

    • 对象的行为依赖于其状态:当一个对象的行为随着其内部状态的改变而改变时,使用状态模式可以清晰地表示这种依赖关系。
    • 复杂的状态逻辑:如果一个对象有复杂的状态逻辑,如多个条件语句决定状态的转换,状态模式可以帮助组织和管理这些逻辑。
    • 状态转换明确:当**存在明确的状态转换规则,**并且这些规则随时间变化时,状态模式可以使得状态转换更加清晰和可管理。
    • 避免对象行为的膨胀:如果一个对象在不同状态下的行为有很大的差异,而这些行为用条件语句表示会使代码复杂难懂,状态模式可以有效地组织这些行为。
  • 实现方式:状态模式通过将每种状态封装成独立的类,并将状态转换的逻辑放在状态类内部来实现,从而使得状态和行为的变化更加透明和一致。

  • 优点:将状态的逻辑分散到对应的状态类中,易于扩展新状态。

  • 缺点:可能会引入很多状态类,增加系统复杂度。

比较

  • 相似之处:两者都是通过封装算法或行为来使得它们可以在运行时更改,同时减少了对象间行为的耦合。
  • 不同之处:策略模式通常用于选择算法或行为的系列中的一个,而状态模式通常用于基于内部状态的场景,其行为随着状态的改变而改变。策略模式的策略通常是静态的,不会随着上下文改变而改变,而状态模式的状态可能随着上下文的变化而转移。

简而言之,策略模式侧重于算法的选择,状态模式侧重于对象状态的演变。

请比较适配器、外观和装饰器设计模式的意图和模式

  1. 适配器模式(Adapter Pattern):
  • 意图:适配器模式的目的是将一个类的接口转换成客户端期望的另一个接口。适配器让原本接口不兼容的类可以一起工作
  • 应用场景:当你希望使用某个类,但其接口与其他代码不兼容时,你可以使用适配器模式。这在整合旧系统或使用第三方库时尤为常见。
  1. 外观模式(Facade Pattern):
  • 意图:外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,使得子系统更容易使用。
  • 应用场景:当你有一个复杂的子系统,并想为子系统中的一组接口提供一个简单的接口时,可以使用外观模式。它常用于简化客户端和复杂系统之间的交互。
  1. 装饰器模式(Decorator Pattern):
  • 意图:装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更为灵活。
  • 应用场景:当你希望在不改变对象接口的前提下,增强或修改对象的行为时,可以使用装饰器模式。这种模式在动态地添加功能以及在运行时选择功能方面非常有效。

总结来说,适配器模式主要用于使现有接口适应新的接口,外观模式用于简化复杂接口,而装饰器模式则用于在不改变接口的情况下增加对象的功能。每种模式在解决特定类型的设计问题方面都有其独特的优势。

参考引用

  1. 南京大学软件学院-2023-高级软件设计(研究生)期末复习参考 - 知乎

  2. 高级软件设计-期末复习 | EagleBear2002 的博客

  3. HeadFirst设计模式(中文版)

  4. 常用设计模式有哪些?