软件构造复习:第五章

目录

    • 可维护性的常见度量指标
    • 聚合度与耦合度(高内聚,低耦合)
    • SOLID:5 classes design principles
      • 单一责任原则/The Single Responsibility Principle(SRP)
      • 开放-封闭原则/The Open-Closed Principle(OCP)
      • Liskov替换原则/The Liskov Substitution Principle(LSP)
      • 接口聚合原则/The Interface Segregation Principle(ISP)
      • 依赖转置原则/The Dependency Inversion Principle(DIP)
    • 设计模式
      • factory method/工厂方法
      • abstract factory
      • proxy/代理模式
      • observer/observable(观察者模式)
      • visitor/访问者模式
      • state/状态模式
      • memento/备忘录模式
    • 语法、正则表达式

可维护性的常见度量指标

  • 圈复杂度(CC):度量代码的结构复杂性;它是通过计算程序流中独立路径的数量来创建的。
  • 代码行数(Lines of Code)(非常粗略的标准)
  • 可维护性指标(MI):计算介于0和100之间的索引值,该值表示维护代码的相对容易程度。高值意味着更好的可维护性。计算依据:1)Halstead容积(HV);2)CC;3)每个模块的平均代码行数(LOC);4)每个模块的注释行百分比(COM)
  • 继承的层次数:越深越不好
  • 类之间的耦合度:越松越好
  • 单元测试的覆盖度

聚合度与耦合度(高内聚,低耦合)

耦合度(Coupling):是模块之间依赖性的度量。如果一个模块中的更改可能需要另一个模块中的更改,则两个模块之间存在依赖关系
模块之间的耦合度由以下因素决定

  • 模块之间的接口数量(数量
  • 以及每个接口的复杂性(质量
    软件构造复习:第五章_第1张图片

聚合度(Cohesion):是衡量模块的功能或职责之间关联程度的一个指标。如果模块的所有元素都朝着同一个目标工作,则该模块具有很高的内聚性。
软件构造复习:第五章_第2张图片
最好的设计在模块内具有高内聚和模块间的低耦合

SOLID:5 classes design principles

OO设计的两大武器
抽象(abstraction):模块之间通过抽象隔离开来,将稳定部分和容易变化部分分开

  • LSP:对外界看来,父类和子类是“一样”的;
  • DIP:对接口编程,而不是对实现编程,通过抽象接口隔离变化;
  • OCP:当需要变化时,通过扩展隐藏在接口之后的子类加以完成,而不要修改接口本身。

分离(separation):Keep It Simple, Stupid(KISS)

  • SRP:按责任将大类拆分为多个小类,每个类完成单一责任/职责,规避变化,提高复用度;
  • ISP:将接口拆分为多个小接口,规避不必要的耦合

单一责任原则/The Single Responsibility Principle(SRP)

责任:变化的原因。

不应有多于1个的原因使得一个类发生变化,即一个类一个责任。

如果一个类包含了多个责任,那么将引起不良后果:

  • 引入额外的包,占据资源
  • 导致频繁的重新配置、部署等

最简单的原则,却是最难做好的原则

开放-封闭原则/The Open-Closed Principle(OCP)

面向变化的OCP
对扩展性的开放:模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化

对修改的封闭模块自身的代码是不应被修改的。扩展模块行为的一般途径是修改模块的内部实现,如果一个模块不能被修改,那么它通常被认为是具有固定的行为。

Key:抽象技术,运用接口和composition/委托
软件构造复习:第五章_第3张图片
Note.一堆if-else/switch-case结构,维护起来非常麻烦。OCP

Liskov替换原则/The Liskov Substitution Principle(LSP)

子类型必须能够替换其基类型(功能上的完全替换)。

派生类必须能够通过其基类的接口使用,客户端无需了解两者之间的差异。

更多了解Liskov替换原则,请点击此查看LSP

接口聚合原则/The Interface Segregation Principle(ISP)

不能强迫客户端依赖于它们不需要的接口:只提供必需的接口。(客户端不应依赖于它们不需要的方法)

“fat”接口具有很多缺点:不够聚合

  • 胖接口可分解为多个小的接口
  • 不同的接口向不同的客户端提供服务
  • 客户端之访问自己所需要的接口

软件构造复习:第五章_第4张图片

依赖转置原则/The Dependency Inversion Principle(DIP)

  • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
  • 抽象不应该依赖于实现细节,实现细节应该依赖于抽象

eg.the “Copy” program
软件构造复习:第五章_第5张图片

class Copy{
	void Copy(OutputStream dev){
		int c;
		while((c=ReadKeyboard())!=EOF){
			if(dev==printer)
				writeToPrinter(c);
			else
				writeToDisk(c);
		}
	}
}

软件构造复习:第五章_第6张图片

interface Reader{
	public int read();
}
interface Writer{
	public int write(c);
}
class Copy{
	void Copy(Reader r,Writer w){
		int c;
		while(c=r.read()!=EOF)
			w.write(c);
	}
}

设计模式

factory method/工厂方法

当client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。

定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从而使一个类的实例化延迟到其子类
软件构造复习:第五章_第7张图片
eg.第一种实现方式

interface TraceFactory{
	Trace getTrace();
	void otherOperation();
}

public class SystemTraceFactory implements TraceFactory{
	public Trace getTrace(){
		...//other operations
		return new SystemTrace();
	}
}

public class FileTraceFactory implements TraceFactory{
	public Trace getTrace(){
		return new FileTrace()
	}
}
//client使用的时候
//...some code...
Trace log1=new SystemTraceFactory().getTrace();
log1.debug("entring log");

Trace log2=new FileTraceFactory().getTrace();
log2.debug("...");

第二种实现方式

interface TraceFactory{
	Trace getTrace(String type);
	void otherOperation();
}

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

Trace log=new Factory().getTrace("system");
log.setDebug(false);
log.debug("...");

静态工厂方法:在方法中加上static。即可以在ADT内部实现,也可以构造单独的静态类。
eg.

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("entring log");

Trace log2=TraceFactory.getTrace("system");
log2.setDebug(true);
log2.debug("...");

相比于通过构造器(new)构造对象

  1. 静态工厂方法可具有指定的名称
  2. 不必在每次调用的时候都创建新对象
  3. 可以返回原返回类型的任意子类型

OCP:对工厂方法进行修改,对client封闭

abstract factory

提供接口以创建一组相关/相互依赖的对象,但不需要指明其具体实现类

Approach:通过一个可调用其他多个工厂的工厂

点击此查看详细的抽象工厂模式内容

Note.创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的object,各产品创建过程对client可见,但“搭配”不能改变。

本质上,Abstract Factory是把多类产品的factory method组合在一起

proxy/代理模式

某个对象比较“敏感”/“私密”/“贵重”,不希望被client直接访问到,故设置proxy,在两者之间建立防火墙

在这里插入图片描述

类型:

  • 远程代理:为一个对象在不同的地址空间提供局部代表(缓存机制)
  • 虚代理:根据需要创建开销很大的对象
  • 保护代理:提供访问保护

eg.
软件构造复习:第五章_第8张图片

public interface Image{
	void display();
}

public class ProxyImage implements Image{
	private Image realImage;
	private String fileName;
	
	public ProxyImage(String fileName){
		this.fileName=fileName;
	}

	@Override
	public void display(){
		if(realImage==null){
			realImage=new RealImage(fileName);
		}
		realImage.display();
	}
}

Client:
Image image=new ProxyImage("pic.jpg");
image.dispaly();

Adaptor VS Proxy

  • Adaptor目的:消除不兼容,目的是B以客户端期望的统一的方式与A建立起联系
  • Proxy目的:隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为”

observer/observable(观察者模式)

  • 一种“发布-订阅”形式,发布方的变化,会通知订阅方
  • 订阅方在发布方注册
  • 通过接口分离两者

eg.

public class Subject{
	private List<Observer> observers=new ArrayList<Observer>();
	private int state;

	public int getState(){return state;}

	public void setState(int state){
		this.state=state;
		notifyAllObservers();//在自己状态变化时,通知所有“粉丝”
	}

	public void attach(Observer observer){
		observers.add(observer);
	}

	private void notifyAllObservers(){
		for(Observer observer:observers){
			observer.update();
			//callback调用“粉丝”的update操作,向粉丝“广播”自己的变化,实际执行delegation
		}
	}
}

public abstract class Observer{
	protected Subject subject;
	public abstract void update();
}

public class BinaryObserver extends Observer{
	public BinaryObserver(Subject subject){
		this.subject=subject;
		this.subject.attach(this);
		//构造时,指定自己的“偶像”subject,把自己注册给它,这是反向代理/委托delegation
	}

	@Override
	public void update(){
		System.out.println("Binary String:"+
			Integer.toBinaryString(
				subject.getState()));
	}
}

//客户端
public class ObserverPatternDemo{
	public static void main(String[] args){
		Subject subject=new Subject();

	new HexaObserver(subject);
	new OctalObserver(subject);
	new BinaryObserver(subject);

	System.out.println("Frist state change:15");
	subject.setState(15);
	System.out.println("Second state change:10");
	subject.setState(10);
	}
}

visitor/访问者模式

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

本质上:将数据和作用于数据上的某种/些特定操作分离开来

/*Abstract elemnt interface(visitable)*/
public interface ItemElement{
	public int accept(ShoppingCartVisitor visitor);
}

/*Concrete element*/
public class Book immplements IteamElement{
		  //声明自己是可以被visit的
	private double price;
	...
	int accept(ShoppingCartVisitor visitor){
		visitor.visit(this);//反向代理
		//将处理数据的功能delegate到外部传入的visitor中
	}
}

public class Fruit implements ItemElemnt{
	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{
			//这里只列出了一种visitor实现
	public int visit(Book book){
	//这个visit操作的功能完全可以在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){
		ShoppingCarVisitor visitor=new ShoppingCartVisitorImpl();
		int sum=0;
		for(ItemElement item:items)
			sum=sum+item.accept(visitor);
		return sum;
	}
}

Iterator VS Visitor

  • 迭代器:以遍历的方式访问集合数据而无需暴露起内部表示,将“遍历”这项功能delegate到外部的iterator对象
  • 访问者:在特定ADT上执行某种特定操作,但该操作不在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活扩展/改变visitor的操作算法,而不影响ADT。

Strategy VS Visitor:二者都是通过delegation建立两个对象的动态联系

  • 但是Visitor强调的时外部定义某种对ADT的操作,该操作与ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client通过它设定visitor操作并在外部调用。
  • 而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类而已。

state/状态模式

状态模式允许在运行时修改对象的行为或状态,每个行为(一组方法构成)用一个状态类表达

详细点击此查看状态模式

memento/备忘录模式

记住对象的历史状态,以便于“回滚”
软件构造复习:第五章_第9张图片
Memento:非常简单的类,只记录一个历史状态

class Memento{
	private State state;

	private Memento(State state){
		this.state=state;
	}

	public State getState(){
		return state;
	}
}
class Originator{
	private State state;

	public void setState(State state){
	//ADT原本的状态转换功能,可能更复杂(例如State模式)
		System.out.println("Originator: Setting state to "+state.toString());
		this.state=state;
	}

	public Memento save(){
	//保存历史状态,delegate到memento去实现
		System.out.println("Originator: Saving to Memento.");
		return new Memento(state);
	}

	public void restore(Memento m){
		//利用传入的Memento对象来恢复历史状态
		state=m.getState();
		System.out.println("Originator: State after restoring from Memento: "+state);
	}
}
class Caretaker{
	private List<Memento> mementos=new ArrayList<>();//保留一系列历史状态
	public void addMemento(Memento m){
		mementos.add(m);//添加一个新的历史状态
	}
	public Memento getMemento(int i){//取回需要回滚的状态
		if(mementos.size()-i<0)
			throw new RuntimeException("Cannot rollback so many back!");
		return mementos.get(mementos.size()-i);
		//但这里有个潜在bug
	}
}
public class Demonstration {
	public static void main(String[] args) {
		Caretaker caretaker = new Caretaker();
		Originator originator = new Originator();
		originator.setState("State2");
		caretaker.addMemento( originator.save() );
		originator.setState("State3");
		caretaker.addMemento( originator.save() );
		originator.setState("State4");
		originator.restore( caretaker.getMemento(2) );
	}
}

语法、正则表达式

语法
使用grammar判断字符串是否合法,并解析成程序里使用的数据结构,通常是递归的数据结构。

url ::=‘http://’ hostname ‘/’
hostname ::= ‘mit.edu’ | ‘stanford.edu’ | ‘google.com’

其中,url是根节点(特殊的非终止符),与’google.com’相似的是终止符,hosname是非终止符

操作

  • 连接: x ::= y z(空格)
  • 重复(0次或多次):x::=y*
  • 选择:x::= y|z
  • 有无:x::= y?
  • 重复(1次或多次):x::= y+
  • 区间:x::= [a-c] equals x::= ‘a’ | ‘b’ | ‘c’
    x ::= [aeiou] equals x::= ‘a’ | ‘e’ | ‘i’ | ‘o’ | ‘u’
  • 不包含区间:x::=[^a-c] equals x::= ‘d’ | ‘e’ | ‘f’ |…

*?+ 优先级最高,连接次之,|最低


正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点

一些表达:
软件构造复习:第五章_第10张图片
Using Regular expressions in Java

主要方法:String.split,String.matches,java.util.regex.Pattern

Pattern对象是对regex正则表达式进行编译之后得到的结果
Matcher对象:利用Pattern对输入字符串进行解析
软件构造复习:第五章_第11张图片
Extract part of an HTML tag:软件构造复习:第五章_第12张图片
软件构造复习:第五章_第13张图片
软件构造复习:第五章_第14张图片
软件构造复习:第五章_第15张图片
flag:详细的正则语言会在后面的博客中会写

你可能感兴趣的:(设计模式,java,正则表达式)