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

目录

一、Creational patterns

1.工厂方法模式

二、Structural patterns

1.Adapter 适配器模式

2.Decorator 装饰器模式

三、Behavioral patterns 

1.Strategy 策略模式

2.Template Method 模板模式

3.Iterator 迭代器模式

4.Visitor模式

四、总结


一、Creational patterns

1.工厂方法模式

工厂方法模式也叫“虚拟构造器”,当用户不知道要创建哪个具体类的实例,或者说不想在用户代码中指明创建实例的时候可以用工厂方法模式来创建。

一般步骤就是先创建一个工厂接口,然后创建一个工厂类implements这个接口,在里面具体实现创建对象的方法。

现在假设我们有一个Trace接口,我们有两种实现方式,一种是SystemTrace一种是FileTrace

interface Trace:

然后我列出其中一个实现(SystemTrace):

另外一个FileTrace也是implements这个Trace接口的,只是说对具体方法的实现稍有不同,这里就不贴出来了。

那这个时候我们一般会选择这么去构造这两个对象:

就是用接口声明然后new一个实例对象,这样做的话将我们的具体实例与客户代码紧紧捆绑在一起了,那客户用起来它就必须知道实例类型才能构造对象了,万一他记不住咋办?此时就可以用工厂方法来改造这个构造方法。

我们先搞一个TraceFactory接口:

interface TraceFactory{
    public Trace getTrace(String type);
}

为了方便起见我就只定义一个getTrace()方法了。 

 以后这个getTrace()方法就是构造方法了,当我们需要构造一个Trace对象时,我们就构造一个工厂对象,然后调用getTrace()方法,这样它就会为我们自动构造我们想要的对象了。

那问题又来了,它是怎么知道你想要构造什么类型的Trace对象的呢?没错,就是这个String type参数决定的。下面我们就来看看具体实例类Factory1的实现:

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

 这里有几个点要注意:

1.Factory1知识TraceFactory接口的一种实现方式,这意味着TraceFactory还可以有多种构造方式

2.用工厂方法构造对象的话就不需要知道具体实现类到底叫SystemTrace还是叫什么别的了,只需要用Factory的gettrace()方法,输入对应参数就解决了。

二、Structural patterns

1.Adapter 适配器模式

用通俗的话来说就是一个参数转换器。

比方说你要调用的实际方法是这个:

但是客户端输入的参数是这样的:

这时候我们就需要一个Adapter来进行转换:

2.Decorator 装饰器模式

当我们在设计一个接口的时候,我们需要其子类具有不同的特性,但是绝大多数的功能其实是共性的。这个时候如果我们仅仅是单纯地去implements的话就会出现大量的重复代码,而且当我们需要不同特性的组合的时候就会产生复杂的继承关系。Java不支持多继承,所以我们要使用装饰器模式来使得某些子类可以具有不同的特性。

举个例子,比如说我们现在有一个Stack接口,里面有push和pop方法

interface Stack{
    void push(Item e);
    Item pop();
}

现在我们需要三种特别的Stack:LockedStack,UndoStack,SecureStack。

这三种Stack都具有某种特点,而且可以组合在一起,比如说 UndoSecureStack等等。

我们要做的首先是实现一个啥也不是的类,如果把特性比作皮肤的话,就是得先弄个原皮:

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

如果把Decorator模式看作穿衣服的过程,我们还需要一个“衣架子”,你可以给它“穿不同的衣服”(加不同的特性)。衣架子就是Decorator类:

public abstract class StacDecorator implements Stack{
    protected final Stack stack;
    //creator
    public StackDecorator(Stack stack){
        this.stack=stack;
    }
    public void push(Item e){
        stack.push(e);
    }
    public Item pop(){
        return stack.pop();
    }
...
}

Decorator类有几个需要注意的地方:

1.所有常规操作来源于rep中stack的操作。可以看到,push 、pop这两个方法都是直接用的stack.xxx。

2.Decorator类是abstract类,这说明有些方法不必实现。这是为了以后的特性预留的。

3.构造方法是关键!构造方法的参数,就相当于是这个Decorator穿的衣服,你用什么stack构造,这个Decorator就具备了某种特性。这意味着我们可以通过构造方法的叠加来使得Decorator具有不同的特性,这个后面可以看出来。

下面实现一个UndoStack:

public class UndoStack extends StackDecorator implements Stack{
    private final Undo 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(){
        ...
    }
    ...
}

首先我们可以看到这个类继承了Decorator。我认为继承这个Decorator的关键在于UndoStack需要有可以“穿衣服”这个特性。具体我们可以看到push、pop方法中都出现了super.xxx,这说明这个类中,常规操作仍然是由它穿的衣服——rep中的stack所完成的。而它的特点则体现在对方法实现的改变,比如说push方法中的log.append语句,以及undo方法。

现在我们假设,这个类如果在构造的时候,构造方法的参数是个SecureStack会怎样?

没错,可以理解为UndoStack穿了个SecureStack的衣服,这样的话这个类就拥有了UndoSecureStack的特性!这样就完成了特性的叠加:

Stack s = new ArrayStack(); //穿个原皮
Stack t = new UndoStack(new ArrayStack());//给UndoStack穿了个原皮,相当于就是个UndoStack
Stack t = new SecureStack(new UndoStack(new ArrayStack()));//给SecureStack穿了个UndoStack的衣服,而这个UndoStack穿着原皮,意味着UndoStack有作为Stack最基本的操作,所以这是个UndoScureStack

Decorator去哪儿了?在这段声明的代码中我们似乎没看到Decorator。你可以这么理解:Decorator是所有需要叠加特性类的父类,只有继承了它,这些类才能“穿衣服”,所以照这么理解,它就相当于一个衣架,啥用没有(bushi,因为最原始的Stack都要穿一个“原皮”,又怎么会直接露出衣架呢?

三、Behavioral patterns 

1.Strategy 策略模式

当我们有很多种不同的算法类完成同一个任务的时候,Strategy模式会自动切换算法,是灵活的。

 咋实现呢?

首先仍然是写一个接口,这里就打个比方比如说你现在要支付,你有很多种付钱的方式,什么刷卡啊现金啊巴拉巴拉...那把你这个有多种方式实现的方法写到这个接口里:

public interface PaymentStrategy{
    public void pay(int amount);
}

然后就是我们具体的方法了,我就列举一种:

public class CreditCardStrategy implements PaymentStrategy{
    //rep...
    //creator ...(这里就是你需要啥参数你就构建啥)
    @Override
    public void pay(int amount){
        System.out.println(amount + " paid with credit card");
    }
}

我这里就是列举了用信用卡支付的方式,具体实现我就不写了,其他的支付方式也差不多就这样子的。

然后主类中的pay方法这么写:

public class ShoppingCart{
    ...
    public void pay(PaymentStrategy paymentMethod){ //委托,具体说是委托中的依赖
           int amount = calculateTotal();
           paymentMethod.pay(amount);
    }
}

这里有个注意的地方就是,主类中这个方法的参数是我们用我们接口声明的对象,也就是说我们可以把任何一个支付策略的实例传进去,然后调用它的pay方法就完事儿了。

2.Template Method 模板模式

模板模式相对来说较为简单,就是你现在要做很多事,然后它们步骤其实都一样,但是具体的方法不同。那模板模式就把共性的步骤在抽象类内公共实现,差异化的步骤在子类中实现。

整个就是使用了一个继承+重写的模式

 

这是一个挑礼物的Process类,顾名思义就是记录挑礼物过程。可以看到里面有一些方法已经实现了,比如说giftWrap(),这玩意儿你无论买什么礼物都得这么做,所以属于共性部分,就直接在这个类实现了。然后还有一些方法没实现,这个就是要子类根据不同情况去实现了。

还有一个点就是最后有个processOrder方法,它规定了这些步骤的顺序。

3.Iterator 迭代器模式

客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型。

具体思路呢就是,首先你希望去遍历的这个ADT,它需要继承一个Iteratable<你的ADT类型>这么一个接口并且重写里面的 Iterator<你的ADT类型> iterator()方法

可以这么理解,就是你继承了一个Iteratable接口,是说你这个ADT是可遍历的。

你重写iterator方法,其实就是iterator的构造方法,返回一个即将被你定义的iterator。(比方说什么FruitIterator之类的)

然后再你这个ADT中你需要构造一个内部类,对,就是你想要定义的自己的iterator类,它必须实现Iterator接口(不然没得玩)

这个iterator类需要实现这几个方法:

这个就依据具体情况来写了,不过一般remove方法就是throw一个 UnsupportedOperationException。这个hasNext方法next方法具体怎么写其实我也不太懂,明天就要考试了我一会儿再看看去(实在不行到时候临场发挥吧....太阳总是会升起的)

4.Visitor模式

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

本质上就是将数据和作用在数据上的某种特定操作分离开来。

首先在你要visit的类里面加个接口:

public class Book{
    ...
    int accept(Visitor visitor){
        visitor.visit(this);
    }
    ...
}

 这个接口方法,就接受Visitor这个类型的参数,然后这个方法的内容也很简单,就是这个visitor调用了自己的visit方法,参数是这个类。

啥意思呢?不着急,先来看Visitor类

按照以往的经验,这个Visitor肯定是个接口,而且这个接口里就有个visit方法,这个方法的参数还是你这个ADT的类型。

如下所示:

public interface shoppingCartVisitor{
    int visit(Book book);
}

 那我们肯定要实现这个接口:

public class ShoppingCartVisitor implements ShoppingCartVisitor{
    public int visit(Book book){
        ...//你想对book类中的数据做的操作
    }
}

这下就全都准备完毕了,当你在main函数中调用的时候:

public class ShoppingCartClient{
    public static void main(String[] args){
        ShoppingCartVisitor visitor = new ShoppingCartVisitor();//更换不同的visitor实现以达成不同的目的。
        Book a = new Book();
        int sum=a.accept(visitor);
            ...
        }
}

Visitor模式的实质呢也是委托,但是这个和Strategy不同的地方在于,这个委托给Visitor的时候是自己当参数的,然后Visitor一顿操作之后还得返回来,其实说是双向的也不错,而Strategy模式就是参数是谁就用谁的方法,它不需要访问这个类,是单向的

四、总结

在面向软件可复用性和可维护性设计的时候,这些都是很基本而且很常用的方法,如果能帮到你一点点那就太好了,如果不能那我就当自己复习啦。希望我们一起学习一起进步! 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值