系列文章目录
哈工大软件构造课程知识点总结(一)
哈工大软件构造课程知识点总结(二)
哈工大软件构造课程知识点总结(三)
哈工大软件构造课程知识点总结(四)
哈工大软件构造课程知识点总结(五)
哈工大软件构造课程知识点总结(六)
文章目录
简介
此文章是2021春哈工大软件构造课程Chapter 11、Chapter 12的知识点总结。
Chapter 11:Design Patterns for Reuse and Maintainability
设计模式的划分
除了类本身,设计模式更强调多个类/对象之间的关系和交互过程,比接口/类复用的粒度更大。
- 创建型模式(Creational patterns)
- 工厂方法模式(Factory method pattern):在不指定具体类的情况下创建对象。
- 结构型模式(Structural patterns)
- 适配器模式(Adapter):允许具有不兼容接口的类通过将自己的接口包装在已经存在的类的接口上来实现协同工作。
- 装饰模式(Decorator):动态添加/重写对象中方法的行为
- 行为类模式(Behavioral patterns)
- 策略模式(Strategy):允许在运行时选择一系列算法中的一个
- 模板模式(Template method):在抽象类中定义一个算法的框架,允许它的子类提供具体行为实现
- 迭代器模式(Iterator):顺序地访问一个对象中的元素同时避免泄露其内部表示
- 访问者模式(Visitor):通过将方法的层次结构移动到一个对象中,将算法(具体实现)与对象结构分开
创建型模式
工厂方法模式
工厂方法也称为虚拟构造器,当客户端不知道要创建哪个具体类的实例,或者不想在客户端代码中指明要具体创建的实例时使用。通过定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
示意图:
代码示例:
/* Abstract product 具体类的接口 */
public interface Trace {
// turn on and off debugging
public void setDebug( boolean debug );
// write out a debug message
public void debug( String message );
// write out an error message
public void error( String message );
}
/* Concrete product 1 具体类1 */
public class FileTrace implements Trace {
private PrintWriter pw;
private boolean debug;
public FileTrace() throws IOException {
pw = new PrintWriter( new FileWriter( "t.log" ) );
}
public void setDebug( boolean debug ) {
this.debug = debug;
}
public void debug( String message ) {
if( debug ) {
pw.println( "DEBUG: " + message );
pw.flush();
}
}
public void error( String message ) {
pw.println( "ERROR: " + message );
pw.flush();
}
}
/* Concrete product 2 具体类2 */
public class SystemTrace implements Trace {
private boolean debug;
public void setDebug( boolean debug ) {
this.debug = debug;
}
public void debug( String message ) {
if( debug )
System.out.println( "DEBUG: " + message );
}
public void error( String message ) {
System.out.println( "ERROR: " + message );
}
}
/* 工厂创建对象接口 */
interface TraceFactory {
public Trace getTrace();
public Trace getTrace(String type);
void otherOperation() {}; // 不仅可以包含工厂方法,还可以实现其他功能
}
/* 工厂类1 */
public class Factory1 implements TraceFactory {
public Trace getTrace() { // 创建SystemTrace类
return new SystemTrace();
}
}
/* 工厂类2 */
public class Factory2 implements TraceFactory {
public getTrace(String type) { // 根据类型决定创建哪个具体产品
if(type.equals(“file”)
return new FileTrace();
else if (type.equals(“system”)
return new SystemTrace();
}
}
/* 客户端代码 */
// 客户端使用“工厂方法”来创建实例
Trace log1 = new Factory1().getTrace();
log1.setDebug(true);
log1.debug( "entering log" );
Trace log2 = new Factory2().getTrace("system");
log2.setDebug(false);
log2.debug("...");
除此之外还有静态工厂方法,既可以在ADT内部实现,也可以构造单独的工厂类。
优点:
- 消除在代码中绑定应用特定类的需求
- 代码仅关注接口类(如例中的Trace),所以它可以与任何用户定义的具体类(如例中的FileTrace、SystemTrace)协同工作
潜在缺点:
- 客户端可能不得不创建一个Creator的子类以创建具体类对象
- 面对软件演化问题(OCP原则——对扩展开放,对修改已有代码封闭 ?)
结构型模式
适配器模式
适配器模式的目的是将某个类/接口转换为客户端期望的其他形式。通过增加一个接口,将已存在的子类封装起来,客户端面向接口编程,从而隐藏了具体子类。
示意图:
示例代码:
/* 原方法实现 */
class LegacyRectangle {
// 参数为左下角坐标、宽、高
void display(int x1, int y1, int w, int h) {...}
}
/* Adapter类实现抽象接口 */
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 Client {
// 对抽象接口编程,与LegacyRectangle隔离
Shape shape = new Rectangle();
public display() {
shape.display(x1, y1, x2, y2);
}
}
装饰器模式
装饰器模式通过继承和委托实现特性的任意组合,避免组合爆炸和大量重复。对每一个特性构造子类,通过委派机制增加到对象上。
示例代码:
interface Stack {
void push(Item e);
Item pop();
}
/* 实现最基础的stack功能 */
public class ArrayStack implements Stack {
... //rep
public ArrayStack() {...}
public void push(Item e) {
...
}
public 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
implements Stack {
private final UndoLog log = new UndoLog();
public UndoStack(Stack stack) {
super(stack);
}
public void push(Item e) {
log.append(UndoLog.PUSH, e); // 增加新特性
super.push(e); // 基础功能通过委托实现
}
public void undo() {
//implement decorator behaviors on stack
}
...
}
/* 具体使用 */
// 创建一个stack
Stack s = new ArrayStack();
// 创建一个undo stack
Stack t = new UndoStack(new ArrayStack());
// 继续装饰,得到secure synchronized undo stack
Stack u = new SecureStack(new SynchronizedStack(new UndoStack(s)));
装饰器在运行时组合功能特性,包含多个协作的对象且能够混合和匹配多层装饰。这些都是使用继承难以做到的。
Java Collections提供的unmodifiableList等是通过装饰器模式实现的。
行为类模式
策略模式
策略模式通过为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例来实现动态切换算法,而不是写死在代码里。
示意图:
代码示例:
/* 抽象接口 */
public interface PaymentStrategy {
public void pay(int amount);
}
/* 具体实现1 */
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");
}
}
/* 具体实现2 */
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);
}
}
/* 客户端代码 */
ShoppingCart cart = new ShoppingCart();
Item item1 = new Item("1234",10);
Item item2 = new Item("5678",40);
cart.addItem(item1);
cart.addItem(item2);
//pay by paypal
cart.pay(new PaypalStrategy("myemail@exp.com", "mypwd"));
//pay by credit card
cart.pay(new CreditCardStrategy(“Alice", "1234", "786", "12/18"));
模板模式
模板模式使用继承和重写实现。做事情的步骤一样,但具体方法不同,共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。
代码示例:
/* 模板类 */
public abstract class OrderProcessTemplate {
public boolean isGift;
public abstract void doSelect(); // 差异化步骤
public abstract void doPayment(); // 差异化步骤
public final void giftWrap() {
System.out.println("Gift wrap done.");
}
public abstract void doDelivery(); // 差异化步骤
public final void processOrder() {
doSelect();
doPayment();
if (isGift)
giftWrap();
doDelivery();
}
}
/* 具体类 */
public class NetOrder
extends OrderProcessTemplate {
@Override
public void doSelect() { … }
@Override
public void doPayment() { … }
@Override
public void doDelivery() { … }
}
/* 客户端代码 */
OrderProcessTemplate netOrder = new NetOrder();
netOrder.processOrder();
OrderProcessTemplate storeOrder = new StoreOrder();
storeOrder.processOrder();
迭代器模式
迭代器模式针对的是客户端遍历被放入容器/集合类的一组ADT对象,同时无需关心容器的具体类型的需求。通过让自己的集合类实现Iterable
接口,并实现自己的独特Iterator
迭代器(hasNext
, next
, remove
),允许客户端利用这个迭代器进行显式或隐式的迭代遍历。
示意图:
示例代码:
public class Pair<E> implements Iterable<E> {
private final E first, second;
public Pair(E f, E s) { first = f; second = s; }
public Iterator<E> iterator() {
return new PairIterator();
}
private class PairIterator implements Iterator<E> {
private boolean seenFirst = false, seenSecond = false;
public boolean hasNext() { return !seenSecond; }
public E next() {
if (!seenFirst) { seenFirst = true; return first; }
if (!seenSecond) { seenSecond = true; return second; }
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
访问者模式
访问者模式将数据和作用在数据上的某种(或某些)特定操作分离开来,运行时进行动态绑定、灵活更改而无需修改被访问的类。通过为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下通过委托接入ADT。
示意图:
示例代码:
/* 元素的抽象接口 */
public interface ItemElement {
public int accept(ShoppingCartVisitor visitor);
}
/* 具体元素类 */
public class Book implements ItemElement{
private double price;
...
// 将处理数据的功能委托给外部传入的visitor
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
public class Fruit implements ItemElement{
private double weight;
...
int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
/* visitor实现 */
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
public int visit(Book book) {
int cost=0;
if(book.getPrice() > 50){
cost = book.getPrice()-5;
}else
cost = book.getPrice();
System.out.println("Book ISBN::"+book.getIsbnNumber() + " cost ="+cost);
return cost;
}
public int visit(Fruit fruit) {
int cost = fruit.getPricePerKg()*fruit.getWeight();
System.out.println(fruit.getName() + " cost = "+cost);
return cost;
}
}
/* 客户端代码 */
public static void main(String[] args) {
ItemElement[] items = new ItemElement[]{
new Book(20, "1234"),new Book(100, "5678"),
new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")};
int total = calculatePrice(items);
System.out.println("Total Cost = "+total);
}
private static int calculatePrice(ItemElement[] items) {
// 若有多个visitor的具体实现,只需在此处切换
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int sum=0;
for(ItemElement item : items)
sum = sum + item.accept(visitor);
return sum;
}
访问者模式与迭代器模式的对比
- 二者都是通过委托建立两个对象的动态联系。
- 访问者模式强调的是外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放
accept(visitor)
即可,客户端通过它设定visitor操作并在外部调用。 - 策略模式强调对ADT内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT功能的重要组成部分,只不过是委托到到外部strategy类而已。
- 访问者模式是站在外部客户端的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),策略模式则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。
Chapter 12:Construction for Robustness and Correctness
健壮性和正确性
相关概念
健壮性:系统在不正常输入或不正常外部环境下仍能够表现正常的程度。要求我们封闭实现细节,限定用户的恶意行为并考虑极端情况。让用户变得更容易:出错也可以容忍,程序内部已有容错机制。
正确性:程序按照spec加以执行的能力,是最重要的质量指标。让开发者变得更容易:用户输入错误,直接结束(不满足precondition的调用)。
正确性倾向于直接报错(error),健壮性则倾向于容错(fault-tolerance)。
对外的接口倾向于健壮;对内的实现倾向于正确。
可靠性 = 健壮性 + 正确性
如何衡量
- 平均失效间隔时间(Mean time between failures, MTBF)——外部观察角度
- 残余缺陷率(Residual defect rates)——内部观察角度
Java中的错误(Error)与异常(Exception)
错误:包括用户输入错误、设备错误、物理限制等几类。程序员通常无能为力,一旦发生,想办法让程序优雅的结束。
异常:程序本身导致的问题,可以捕获、可以处理。
异常处理
异常是程序执行中的非正常事件,程序无法再按预想的流程执行。异常可以将错误信息传递给上层调用者,并报告“案发现场”的信息,是return
之外的第二种退出途径。若找不到异常处理程序,整个系统最后会完全退出。
异常的分类
异常是从Throwable
派生的,主要分为运行时异常(RuntimeException)和其他异常两大类。
- 运行时异常由程序员在代码里处理不当造成,如果在代码中提前进行验证就可以避免;
- 其他异常由外部原因造成,程序员无法完全控制,即使在代码中提前加以验证也无法完全避免。