软件构造--Chapter10-12小结

可维护性常见的度量指标

可维护性–软件系统的易维护性或者可以修改组件以纠正故障、提高性能或其他属性,或适应变化的环境;
可扩展性–软件设计/实施考虑到未来的增长,并被视为系统扩展能力和所需工作水平的一个系统性度量实施扩展;
灵活性–软件能够根据用户需求、外部技术和社会需求轻松更改环境等;
适应性–交互式系统(自适应系统)的能力,该系统可以根据获取的有关其用户及其环境的信息,将其行为适应单个用户;
可管理性–监控和维护软件系统的效率和容易程度,以保持系统的性能、安全性和平稳运行;
可支持性–软件的保存效率部署后运行,基于包括质量文档、诊断信息和知识渊博且可用的技术人员在内的资源;

聚合度与耦合度

耦合度coupling是衡量模块之间依赖的指标,取决于模块间的接口数目,每个接口的复杂度。
内聚度cohesion是衡量函数或模块职责的相关联的程度的指标,高内聚的模块所有元素的工作目标相同。
在这里插入图片描述
要求高内聚、低耦合。

SOLID

SOLID是五个类的设计原则,即SRP单一责任原则、OCP开放-封闭原则、LSP替换原则、DIP依赖转置原则、ISP接口聚合原则。

SRP单一责任原则

ADT中不应该有多于1个原因让其发生变化,否则就拆分开。
一个类有一个责任,如果有多个责任,可能要引入额外的包、占据资源、导致频繁重新配置、部署等。
在这里插入图片描述

OCP开放-封闭原则

模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化。
模块自身代码是不应该被修改的,扩展模块行为的一般途径是修改模块的内部实现,如果一个模块不能被修改,那它常备认为具有固定的行为。
关键是抽象技术,改变类的行为用继承和委托机制。
在这里插入图片描述

LSP替换原则

子类型必须能够替换其基类型,派生类必须能够通过基类型接口使用,client无需了解二者差异。(Chapter9)

ISP接口隔离原则

只提供client必须的接口,避免接口污染、胖接口。
将胖接口分解为多个小接口,不同client使用不同的接口,只能访问所需要的端口。
在这里插入图片描述

DIP依赖转置原则

高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
抽象不应该依赖于实现细节,实现细节应该依赖于抽象。
委托的时候要通过interface建立联系。

语法、正则表达式

终止节点、叶结点、终止符为语法解析树的叶子结点,无法向下扩展。
在这里插入图片描述
连接(以空格表示)、重复(以*表示)、选择(或,以|表示)为三个基本操作符
在这里插入图片描述
其他操作符如?(单字符存在或不存在),+(克林闭包),[…](穷举的或),[^…](穷举的集合补集的或) 如下
在这里插入图片描述
其中?+优先级最高,连接次之,|最低
语法树,对应派生过程(示例)
在这里插入图片描述
正则语法,简化后可以表达为一个产生式而不包含任何非终止节点。
url、markdown是正则的,但是html不是正则的。
在这里插入图片描述
Pattern对象是对regex正则表达式编译后的结果,Matcher对象是利用Pattern对象对输入字符串进行解析。
在这里插入图片描述

设计模式

adapter模式

将某个类/接口转化为client期望的其他形式,解决类之间接口不兼容问题,通过增加一个接口,将已存在的子类封装,client面向接口编程,隐藏具体子类。
例如LegacyRectangle提供的display应该接收x,y,w,h分别是左上角坐标、宽、高,但是client期望获得左上角坐标、右下角坐标。
在这里插入图片描述

decorator模式

每个子类实现不同的特性,需要特性的任意组合时,继承会使大量代码重复,组合爆炸。
为对象增加不同侧面的特性,对每一个特性构造子类,通过委托机制增加到对象上,以递归的方式实现。
接口定义装饰物执行的公共操作。起始对象在其基础上增加功能(装饰),将通用方法放到此对象中。

interface Stack{//最基础的stack功能
	void push(Item e);
	Item pop();
}

public class ArrayStack implements Stack{
	public ArrayStack(){...}
	public void push(Item e){...}
	public Item pop(){...}
	...
}

public abstract class StackDecorator implements Stack{//用于decorator的基础类
	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
	}
	...
}

使用如下:

Stack s = new ArrayStack();
Stack t = new UndoStack(new ArrayStack());
Stack t = new SecureStack(new SynchronizedStack(new UndoStack(s)));

另一个例子如下:

public interface IceCream { //顶层接口
	void AddTopping();
}

public class PlainIceCream implements IceCream{ //基础实现,无填加的冰激凌
	@Override
	public void AddTopping() {
		System.out.println("Plain IceCream ready for some 
toppings!");
	}
}

/*装饰器基类*/
public abstract class ToppingDecorator implements IceCream{
	protected final IceCream input;
	public ToppingDecorator(IceCream i){
		this.input = i; 
	}
	public abstract void AddTopping(); //留给具体装饰器实现
}

public class CandyTopping extends ToppingDecorator{
	public CandyTopping(IceCream i) {
		super(i);
	}
	public void AddTopping() {
		input.AddTopping(); //decorate others first
		System.out.println("Candy Topping added!");
	}
}
public class NutsTopping extends ToppingDecorator {
    public NutsTopping(IceCream i) {
        super(i);
    }
    public void AddTopping() {
        input.AddTopping(); //decorate others first
        System.out.println("Nuts Topping added!");
    }
}

public class PeanutTopping extends ToppingDecorator {
    public PeanutTopping(IceCream i) {
        super(i);
    }

    public void AddTopping() {
        input.AddTopping(); //decorate others first
        System.out.println("Peanut Topping added!");
    }
}
	
public class Client {
	public static void main(String[] args) {
	IceCream toppingIceCream = new NutsTopping(
	new PeanutTopping(
	new CandyTopping(
	new PlainIceCream())));
	toppingIceCream.AddTopping();

结果如下:
在这里插入图片描述
Decorator在运行时合成特性,继承在编译时组合功能;装饰器由多个协作对象组成,继承生成一个单一的、类型明确的对象;可以混搭多种装饰,多重继承在概念上很困难。

strategy模式

有多种不同的算法来实现同一个任务,但client根据需要动态切换算法,不是写死在代码中。
为不同实现算法构造抽象接口,利用委托机制,运行时动态传入client倾向的算法实例。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接口有不同的实现,实例包含有接口,client通过调用方法时传入实例化参数不同(接口不同的实现),调用不同的实现方法。

template模式

做事情步骤一样,具体实现不同。共性的步骤在抽象类内实现,差异化步骤在各个子类实现。模板定义了算法步骤。
使用继承和重写实现模板。

public abstract class CarBuilder {
	protected abstract void BuildSkeleton();
	protected abstract void InstallEngine();
	protected abstract void InstallDoor();

// Template Method that specifies the general logic
	public void BuildCar() { //通用逻辑
		BuildSkeleton();
		InstallEngine();
		InstallDoor();
	} 
}

public class PorcheBuilder extends CarBuilder {
	protected void BuildSkeleton() {
		System.out.println("Building Porche Skeleton");
	}
	protected void InstallEngine() {
		System.out.println("Installing Porche Engine");
	}
	protected void InstallDoor() {
		System.out.println("Installing Porche Door");
	} 
}

public class BeetleBuilder extends CarBuilder {
	protected void BuildSkeleton() {
		System.out.println("Building Beetle Skeleton");
	}
	protected void InstallEngine() {
		System.out.println("Installing Beetle Engine");
	}
	protected void InstallDoor() {
		System.out.println("Installing Beetle Door");
	}
}

public static void main(String[] args) {
	CarBuilder c = new PorcheBuilder();
	c.BuildCar();
	c = new BeetleBuilder();
	c.BuildCar();
}

结果如下:
在这里插入图片描述

iterator/iterable模式

客户端希望对放入容器/集合类的一组ADT对象进行遍历访问,而无需关心容器的具体类型。
Iterable接口,实现该接口的容器对象是可迭代遍历的,Iterator接口,迭代器。

public interface Iterable<T>{
	...
	Iterator<T> iterator();
}
public interface Iterator<E>{
	boolean hasNext();
	E next();
	void 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();
		}
	}
}

factory method模式

虚拟构造器,当client不知道/不确定要创建哪个具体类的示例,或者不想在client中指明具体创建的实例,使用工厂方法。
定义一个用于创建对象的接口,让该接口的子类型决定实例化哪一个类,从而使一个类的实例化延迟其子类。
静态工厂方法既可以在ADT中,也可以单独构造工厂类,其方法可有具体指定的更有意义的名称,不必在每次调用时创建新的工厂对象,可以返回原返回类型的任意子类型。

public class SystemTraceFactory{
	public static Trace getTrace() {
		return new SystemTrace();
	}
}
public class TraceFactory {
	public static Trace getTrace(String type) {
		if(type.equals("file")
			return new FileTrace();
		else if (type.equals("system")
			return new SystemTrace();
	} 
}

//... some code ...
Trace log1 = SystemTraceFactory.getTrace();
log1.setDebug(true);
log1.debug( "entering log" );
Trace log2 = TraceFactory.getTrace("system");
log1.setDebug(true);
log2.debug("... ");

visitor模式

对特定类型object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。
本质上是将数据和对数据特定的操作分离开。
为ADT预留一个可扩展功能的接入点,外部实现功能代码在不改变ADT本身情况下通过委托接入ADT。

/* Abstract element interface (visitable) */
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);
	} 
}
/* Abstract visitor interface */
public interface ShoppingCartVisitor { 
	int visit(Book book); 
	int visit(Fruit fruit); 
} 

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 class ShoppingCartClient {
	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) {
		ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
		int sum=0;
		for(ItemElement item : items)
			sum = sum + item.accept(visitor);
		return sum;
	} 
}

迭代器以遍历方式访问集合数据无需暴露内部表示,将遍历功能委托到外部iterator对象;
visitor在ADT上执行特定操作,不在ADT内部实现,委托到独立的visitor对象,client灵活扩展/改变visitor算法,不影响ADT。

visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。

健壮性和正确性

Robustness健壮性,系统在不正常输入或不正常外部环境下仍能够表现正常的程度。
处理未期望的行为和错误终止;即使终止执行,要准确/无歧义向用户展示全面错误信息。
Correctness正确性,程序按照spec加以执行的能力,最重要的质量指标。
正确性,不给用户错误的结果,倾向于直接报错;健壮性,尽可能保持软件运行而不是总是退出,倾向于容错。
在这里插入图片描述
对外的接口倾向于健壮性,对内的实现倾向于正确。
MTBF是指相邻两次故障之间的平均工作时间,对于可修复系统是导致系统不可使用的failure;出现问题仍然可运行则不作为failure。
MTTF是不可修复系统的故障前平均时间。

Throwable

在这里插入图片描述

Error/运行异常、其他异常

内部错误程序员无法解决,一旦发生要让程序结束。
异常由程序导致,可以捕获和处理。

异常是程序执行中的非正常事件,导致程序无法按预想的流程执行。异常将错误信息传递给上层调用者并报告案发现场信息。
异常是return外的另一种退出途径,找不到异常处理程序则系统退出。

运行时异常是由程序员的代码处理不当造成,例如类型转换,数组越界,空指针异常等;其他异常是外部原因造成的,例如文件操作。

Checked和unchecked异常

在这里插入图片描述
编译器可以帮助检查程序是否抛出或处理可能的异常(checked),错误和运行时异常无法由编译器检查(unchecked)。
unchecked异常不用try…catch等处理,可以捕获和处理,但是也无法解决。
checked异常必须捕获指定处理器handler,否则编译不通过。

Checked异常处理机制

throws声明本方法可能会发生某些异常;
throw主动抛出某些异常;
try…catch…finally捕获并处理某些异常。
尽量用unchecked异常处理编程错误,它们会自动在出现的地方挂起程序并打印异常信息。
在这里插入图片描述
在规约中可以用@throws声明可能存在的checked异常。
方法应该抛出其他函数传来的异常、自己造成的异常。
子类型重写的方法,不应该抛出比父类型抛出异常更宽泛。可以抛出更具体的异常、不抛出异常。(子类型方法返回值、异常类型-协变,子类型的更宽泛;子类型方法参数-逆变,子类型的更具体)

一旦抛出异常,则控制权不会给client,无需考虑返回错误代码。

异常发生但找不到处理器就会终止执行程序,在控制台打印出stack trace。

try{
	//code
}catch(ExceptionType e){
	//handler for this type
}

如果本方法不出来异常,则可以传递给调用方处理。处理不了传给上家。
父类型若没有抛出异常,则子类型必须捕获所有的checked异常。

catch中也可以抛出异常,更改异常类型,便于client获取信息、处理。

try {
access the database
}catch (SQLException e) {
	throw new ServletException("database error: " + e.getMessage());
}

finally部分的代码,是否捕获异常都会被执行,常常用于对资源的恰当清理。

class Indecisive {
	public static void main(String[] args) {
			System.out.println(decision());
	}
static boolean decision() {
	try {
		return true;
	} finally {
		return false;
		}
	}
}

上述例子的运行结果为false。

当异常发生会显示如下信息:(可能Enter等不会显示)
在这里插入图片描述

自定义异常类

继承java.lang.Exception实现自己的checked异常类。

public class FooException extends Exception {
	public FooException() { 
		super();
	}
	public FooException(String message) { 
		super(message);
	}
	public FooException(String message, Throwable cause) { 
		super(message, cause); 
	}
	public FooException(Throwable cause) {
		super(cause); 
	}
}

继承java.lang.RuntimeException实现自己的unchecked异常类。

断言

在开发阶段的代码中嵌入断言,检验某些“假设”是否成立,若成立表明程序运行正常,否则表明存在错误。
断言包括boolean的表达式、不符合展示的信息。

assert condition;
assert condition:message;

使用场景:
内部不变量,例如assert x>0
表示不变量,例如checkRep()
控制流不变量,例如switch-case
方法的前置条件、后置条件

public double sqrt(double x){
	assert x >= 0;
	double r;
	...//compute result r
	assert Math.abs(r*r-x) < .0001;
	return r;
}

使用断言可以在开发阶段调试程序、尽快避免错误,在具体使用时要关闭断言。
断言倾向于正确性,处理不能发生的情况;
异常倾向于健壮性,处理预料到的不正常情况。

防御式编程

对外部数据源要仔细检查,例如文件、网络数据、用户输入等。
对每个函数输入参数合法性要检查,决定如何处理非法输入。
类的public方法接收到外部数据被认为是ditry的,要处理好传递给private方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海质粒ABCC9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值