欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

软件构造笔记四:考前学习(预习)设计模式

程序员文章站 2022-03-10 14:15:31
...

缘起

出现各种各样设计模式的原因,还是为了达到软件系统的质量要求。

软件系统的外部质量因素包括:

External 1: Correctness(正确性),正确就是满足spec,这是软件开发最重要的因素,一个可用的软件一定是正确的,所以首要保证软件的正确性,其他的都可以做妥协、让步,但只有这一项不可妥协。

External 2: Robustness(健壮性),通过抛出异常然后处理异常等方式让出错的程序恢复到正常的执行流程上。

External 3: Extendibility(易扩展性),要便于软件功能的增加/扩展(ADT、OOP、留下一个Visitor),降低未来修改软件时的成本。

External 4: Reusability(复用性),在异性之间尽可能地寻找共性,以便于未来可以直接使用现在写的这段代码。这样可以降低软件地开发成本。

External 5: Compatibility(兼容性),在不同的环境下都是可用的,不同的软件系统之间相互可容易的集成。

External 6: Efficiency(效率),不要过早的优化,性能在没有正确性保障的条件下是没有意义的。

External 7: Portability(可移植性),软件可方便的在不同的技术环境之间移植。

External 8: Ease of use(易用性),学习成本低,结构简单、清晰,易于使用。

External 9: Functionality(功能性),功能过多会导致易用性的降低。主要功能要首要提升质量。

External 10: Timeliness(时效性),软件要能够在交付时间之前完成开发交给使用者。

External 10++: Other qualities,Verifiability (可验证性),Integrity (完整性),Repairability (可修复性),Economy (经济性)。

针对这些外部质量因素就会有相应的设计模式出现。

面向复用性

继承与LSP

对子类型的要求:
Preconditions cannot be strengthened in a subtype. 前置条件不能强化
Postconditions cannot be weakened in a subtype. 后置条件不能弱化
Invariants of the supertype must be preserved in a subtype. 不变量要保持
Contravariance of method arguments in a subtype 子类型方法参数:逆变
Covariance of return types in a subtype. 子类型方法的返回值:协变
No new exceptions should be thrown by methods of the subtype, except where those exceptions are themselves subtypes of exceptions thrown by the methods of the supertype. 异常类型:协变

关键字 extends

class T {
void b( ) throws Throwable {}
}
class S extends T {
@Override 
void b( ) throws IOException {}
}
class U extends S {
@Override 
void b( ) {}
} 

委托

发生在对象层次,一个对象向另一个对象请求方法。

class RealPrinter {
void print() {
System.out.println("The Delegate");
}
}
class Printer {
RealPrinter p = new RealPrinter();
void print() {
p.print();
}
}
Printer printer = new Printer();
printer.print();

Dependency:临时性的delegation,通过方法的参数或者在方法的局部中使用发生
联系。
Association:永久性的delegation,将被委托的对象绑定在委托者类内部,在类内创建,但不定义。
Composition:更强的association,但难以变化,直接在委托者类内部定义被委托者。
Aggregation:更弱的association,可动态变化,对象存在于另一个对象之外,是在另一个对象之外创建的,所以它作为一个参数传递给构造器。

组合

继承和委托组合起来。

面向可维护性

SOLID:
▪ 抽象(abstraction):模块之间通过抽象隔离开来,将稳定部分和容易
变化部分分开
– LSP:对外界看来,父类和子类是“一样”的;
– DIP:对接口编程,而不是对实现编程,通过抽象接口隔离变化;
– OCP:当需要变化时,通过扩展隐藏在接口之后的子类加以完成,而不要修
改接口本身。
▪ 分离(Separation): Keep It Simple, Stupid (KISS)
– SRP:按责任将大类拆分为多个小类,每个类完成单一职责,规避变化,提
高复用度;
– ISP:将接口拆分为多个小接口,规避不必要的耦合。

面向可复用性和可维护性的设计模式

工厂方法模式

当client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
将构造器设为接口,用接口实现类中的工厂方法构造对象。使用时不会一次性完成对象的构造,而是一条一条调用工厂方法构造对象。

适配器模式

通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。

interface Shape {
void display(int x1, int y1, int x2, int y2);
}
class Rectangle implements Shape {
void display(int x1, int y1, int x2, int y2) {
new LegacyRectangle().display(x1, y1, x2-x1, y2-y1); 
}
}
class LegacyRectangle {
void display(int x1, int y1, int w, int h) {...}
}
class Client {
Shape shape = new Rectangle();
public display() {
shape.display(x1, y1, x2, y2);
}
}

装饰器模式

为对象增加不同侧面的特性,对每一个特性构造子类,通过委派机制增加到对象上。自身委派自身。

interface Stack {
void push(Item e);
Item pop();
}
public abstract class StackDecorator implements Stack {
protected final Stack stack;
public StackDecorator(Stack stack) {
this.stack = stack;
}
public void push(Item e) {
stack.push(e);
}
public Item pop() {
return stack.pop();
}
...
}
public class UndoStack extends StackDecorator {
private final UndoLog log = new UndoLog();
public UndoStack(Stack stack) { 
super(stack); 
}
public void push(Item e) {
super.push(e);
log.append(UndoLog.PUSH, e);
}
public void undo() {
//implement decorator behaviors on stack
}
...
}

构造对象时,

To construct a plain stack:
– Stack s = new ArrayStack();
▪ To construct an undo stack:
– Stack t = new UndoStack(new ArrayStack());
▪ To construct a secure synchronized undo stack:
– Stack t = new SecureStack(
new SynchronizedStack(
new UndoStack(s))
t.push(e);

又一个例子:

public class Client {
public static void main(String[] args) {
IceCream a = new PlainIceCream(); 
IceCream b = new CandyTopping(a); 
IceCream c = new PeanutTopping(b); 
IceCream d = new NutsTopping(c); 
d.AddTopping();
//or
IceCream toppingIceCream = 
new NutsTopping(
new PeanutTopping(
new CandyTopping(
new PlainIceCream()
)
)
);
toppingIceCream.AddTopping();
}

策略模式

为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。

public interface PaymentStrategy {
public void pay(int amount);
}
public class CreditCardStrategy implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public CreditCardStrategy(String nm, String ccNum, 
String cvv, String expiryDate){
this.name=nm;
this.cardNumber=ccNum;
this.cvv=cvv;
this.dateOfExpiry=expiryDate;
}
@Override
public void pay(int amount) {
System.out.println(amount +" paid with credit card");
}
}
public class PaypalStrategy implements PaymentStrategy {
private String emailId;
private String password;
public PaypalStrategy(String email, String pwd){
this.emailId=email;
this.password=pwd;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using Paypal.");
}
}
public class ShoppingCart {
...
public void pay(PaymentStrategy paymentMethod){
int amount = calculateTotal();
paymentMethod.pay(amount);
}
}

模板模式

共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。使用继承和重写实现模板模式。
在使用方法时换引用,调整引用顺序,调用不同方法。
抽象类+具体实现子类

迭代模式

让自己的集合类实现Iterable接口,并实现自己的独特Iterator迭代器(hasNext, next, remove),允许客户端利用这个迭代器进行显式或隐式的迭代遍历。

Visitor

对特定类型object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。

public interface ItemElement {
public int accept(ShoppingCartVisitor visitor);
}
/* Concrete element */
public class Book implements ItemElement{
private double price;
...
int accept(ShoppingCartVisitor visitor) {
visitor.visit(this);
}
}
public class Fruit implements ItemElement{
private double weight;
...
int accept(ShoppingCartVisitor visitor) {
visitor.visit(this);
}
}

定义接收方法,与访问方法相对应。

总结

迭代器理解得最差,觉得设计模式选好能解决很大的问题。不同情况使用不同的设计模式,能省去很多不必要的空间、时间、删改。