软构学习——几种设计模式的理解
文章目录
1.创建型模式
工厂方法模式
- 当client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。
- 定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
其实就是创建一个另外的类,这个类有一个可以返回目标类对象的方法。通过这种方式能够把,对象的创建与对象的名字分离开,用户可以不用知道所需要类的名字,只要需要知道工厂类,以及相关方法即可。除此之外,这种方式还使得更换目标类的具体实现时,不影响客户端代码,也就是说客户端代码不用改。
example
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 );
}
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 );
}
}
public class SystemTraceFactory implements TraceFactory {
public Trace getTrace() {
… //other operations
return new SystemTrace();
}
}
public class FileTraceFactory implements TraceFactory {
public Trace getTrace() {
return new FileTrace();
}
}
静态工厂方法
既可以在ADT内部实现,也可以构造单独的工厂类。
public class SystemTraceFactory{
public static Trace getTrace() {
return new SystemTrace();
}
}
相比于通过构造器(new)构建对象:
- 静态工厂方法可具有指定的更有意义的名称 。
- 不必在每次调用的时候都创建新的工厂对象 。
- 可以返回原返回类型的任意子类型。
Open-Closed Principle (OCP)
- 对扩展的开放,对修改已有代码的封闭
2.结构性模式
(1)适配器模式(adapter)
- 将某个类/接口转换为client期望的其他形式
- 解决类之间接口不兼容的问题
- 通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。
example
interface Shape {
void display(int x1, int y1, int x2, int y2);
}
class LegacyRectangle {
void display(int x1, int y1, int w, int h) {...}
}
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 {
Shape shape = new Rectangle();
public display() {
shape.display(x1, y1, x2, y2);
}
}
其实本质上就是对原来的类进行更改,但不想写重复的代码,利用复用的知识,也就是委派或继承,复用原本的功能,增加新的功能。
(2)装饰器模式(decorator)
- 用每个子类实现不同的特性
- 需要特性的任意组合
以栈类为例,现在我们需要具有各种功能的栈,比如取消操作、打印信息等。
我们还是不想写重复代码,利用继承是否可行?
显然不行,这会组合爆炸,且仍有大量的代码重复。
那么就需要委派的方法了
为对象增加不同侧面的特性,对每一个特性构造子类,通过委派机制增加到对象上,以递归的方式实现。形象的描述就是穿衣服,每穿一层衣服,就多一些功能。
**接口:**定义装饰物执行的公共操作,就是最原本的功能。
**起始对象:**在其基础上增加功能(装饰),将通用的方法放到此对象中,就是有最基础功能的实现类。
**Decorator抽象类:**是所有装饰类的基类,里面包含的成员变量component 指向了被装饰的对象。
example
interface Stack {
void push(Item e);
Item pop();
}
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 {
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
}
...
}
client
Stack t = new SecureStack(new SynchronizedStack(new UndoStack(s));
t.push(e);
从这个例子我们能明显的看出来它是一层一层的套娃的模式。把额外的功能一点一点的加到对象上。
类似的例子还有
- static List unmodifiableList(List lst);
- static Set unmodifiableSet( Set set);
- static Map unmodifiableMap( Map map);
3.行为类模式(Behavioral)
(1)策略模式(strategy)
有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里。
显然继承依然做不到,我们还得需要委派,为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。
其实跟装饰器模式很像,装饰器是一层一层的穿衣服而策略模式是换衣服穿,因此它也比装饰器模式的结构简单。
example
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);
}
}
client
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"));
(2)模板模式(template)
做事情的步骤一样,但具体方法不同。
共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。使用继承和重写实现模板模式。
example
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();
}
这也是白盒框架的模式。
(3)迭代器(iterator)
客户端希望对放入容器/集合类的一组ADT对象进行遍历访问,而无需关心容器的具体类型 。
也就是说,不管对象被放进哪里,都应该提供同样的遍历方式。
Iterator pattern:让自己的集合类实现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();
}
}
}
(4)Vistor(感觉不知道怎么翻译好)
对特定类型object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。
本质上:将数据和作用于数据上的某种/些特定操作分离开来。
为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下在需要时通过delegation接入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;
}
}
看起来有点晕,其实就是套娃。计算花费的功能类本身就能实现,但这个功能可能现在不需要,提前加上了就很冗余,我只留个接口就显的干净许多。而且还能实现其他功能,同时我原来的代码也不用动。这就是vistor的好处。
vistor VS iterator
二者都是通过delegation建立两个对象的动态联系 – 但是Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client 通过它设定visitor操作并在外部调用。
而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。 这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类而已。
区别:visitor是站在外部client的角度,灵活增加对ADT的各种不同操 作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度, 灵活变化对其内部功能的不同配置。