只会设计模式,现在可能还真不够...

736 阅读5分钟

只会设计模式,现在可能还真不够...

利用模式,我们可以让一个解决方案重复使用,而不是重复造轮子。
(With patterns,you can use the solution a million times over,without ever doing it the same way twice.)             ——克里斯托佛·亚历山大

写代码容易,写出优雅的代码难,同样写出易于维护、容易扩展、结构清晰的代码应该是每一位开发者的目标,学习设计模式,并加以合理的使用能让我们离这个目标更近一步。然而在实际工作如果一板一眼的去套用模式进行相关编码开发的话,不然发现模块中会增加一大把新的接口或类的定义,这种弊病尤其是一个大工程中多个模块需要同样的模式时显得更为突出,那如何解决降低设计模式的引入带来的类似问题,这里分享一个可供参考实际参考的方式,本文将使用Java语言(其他语言类似),利用Lambda表达式改善面向对象的设计模式。

常见设计模式改造

策略模式

案例描述

水果店筛选苹果,用户可以根据苹果颜色、重量等多种策略进行筛选。

  1. 定义策略接口
//封装了对选择苹果的策略
public interface ApplePredicate {
    //具体算法交给子类去实现
    boolean test (Apple apple);
}
  1. 具体筛选算法实现
//颜色算法
public class AppleGreenColorPredicate implements  ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}
//重量算法
public class AppleHeavyWeightPredicate implements  ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight()>150;
    }
}
  1. 根据抽象条件进行筛选
public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
        List<Apple> result = new ArrayList<>();
        //行为参数化
        for (Apple apple : inventory) {
            if (p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }

//可以通过传入不同的策略实现来达到筛选水果的目的
List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());         
List<Apple> greenApples = filterApples(inventory, new AppleGreenColorPredicate());

这种方式有个弊端:每一个策略都需要定义一个具体实现类去实现某个算法,导致增加很多类。 4. 使用匿名类优化

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {   
            @Override
            public boolean test(Apple apple){      
            return "green".equals(apple.getColor()); 
            }
        });

改善之后

  1. 使用Lambda表达式改造,显然比修改前代码干净了许多
List<Apple>result= filterApples(inventory, (Apple apple)-> "red".equals(apple.getColor()));
  1. 使用函数式接口+泛型,让筛选支持其他类型的水果
 public static<T> List<T> filter(List<T> list,Predicate<T> p){
        List<T> result =new ArrayList<>();
        for (T e : list) {
            if (p.test(e)){
                result.add(e);
            }
        }
        return result;
    }

注意:Predicate接口是Java8 提供的函数式接口,无需定义可以直接拿来使用

模板方法

案例描述

假设有一个简单的在线商铺系统,里面有一个操作流程:客户先输入账号,系统查询账户信息,最后完成一些针对用户相关营销操作。不同分店有着不同的方式营销方式,比如说发放红包、推送广告。这里可以使用抽象类的方式来实现这个简单功能。

public abstract class OnlineShop {
    public void processCustomer(String account){
        //根据账号查询客户
        Customer c =DBUtils.getCustomerByAccount(account);
        //营销方式
        promotionForCustomer(c);
    }
    
    //子类去实现不同的营销方式
    abstract void promotionForCustomer(Customer c);
}

考虑到方法 promotionForCustomer 是无返回值的,我们可以使用函数式接口Consumer结合Lambda表达式来实现上述场景,具体代码如下:

  public void processCustomer(String account,Consumer<Customer> promotion){
        Customer c =DBUtils.getCustomerByAccount(account);
        //accept 方法是函数式接口 Consumer 内唯一方法
        promotion.accept(c);
    }

现在可以方便的通过传递Lambda表达式,来植入不同的行为了,不需要继承OnlineShop类了

//注意:现在OnlineShop 类已无其他抽象方法了,可以不用申明为抽象类,直接实例化即可
new OnlineShop().processCustomer("xs",c->System.out.println("发送红包。。" + c.getName()));

观察者模式

案例描述 有一个简单的消息推送系统,系统会不定期推送一些消息,用户关注了就能收到推送消息,不同关注者有着不同的消息处理方式。实现思路大致如下:

  1. 定义一个抽象观察者接口,用于处理接受到的消息
public interface Observer {
    void handler (String msg);
}
  1. 定义一个被观察者接口
public interface Subject {  
    //注册关注者
    public void registerObserver(Observer o);
    //向关注者推送消息
    public void notifyObserver(String msg);  
}
  1. 被观察者具体实现,用于注册观测者,以及推送消息
public interface SubjectImpl implements Subject{  
    private final List<Observer> observers = new ArrayList<>();
    //注册关注者
    public void registerObserver(Observer o){
        this.observers.add(o);
    }
    //向关注者推送消息
    public void notifyObserver(String msg){
        observers.forEach(o -> o.handler(msg));
    }  
}
  1. 不同的关注者具体实现
public class A implements Observer {    
    @Override
    public void handler (String msg) {
        this.message = message;
        //不同消息处理
        System.out.println("小A收到推送消息: " + message);
    }
}

public class B implements Observer {    
    @Override
    public void handler (String msg) {
        this.message = message;
        //不同消息处理
        System.out.println("小B收到推送消息: " + message);
    }
}

public class B implements Observer {    
    @Override
    public void handler (String msg) {
        this.message = message;
        //不同消息处理
        System.out.println("小C收到推送消息: " + message);
    }
}
  1. 消息通知具体功能实现
Subject s =new SubjectImpl();
f.registerObserver(new A());
f.registerObserver(new B());
f.registerObserver(new C());
f.notifyObserver("这是一条没有任何意义的消息~~~");

使用Lambda表达式,避免定义过多的观察者实现类

Subject s =new SubjectImpl();
s.registerObserver(msg->{System.out.println("小A收到推送消息: " + message);})
s.registerObserver(msg->{System.out.println("小B收到推送消息: " + message);})
s.registerObserver(msg->{System.out.println("小C收到推送消息: " + message);})

其它模式

其他设计模式如果在业务不是特别复杂的情况下,同样可以利用Lambda表达式结合Java内置的函数式接口进行优化的,比如说工厂模式可以考虑使用Supplier接口获取构造的对象。总之找准函数式接口是最关键的一步,大家有时间可以找一些其他模式进行演练。

常见的函数式接口

接口输入参数返回类型说明
UnaryOperatorTT一元函数,输入输出类型相同
PredicateTboolean断言
ConsumerT/消费一个数据,只有输入没有输出
Function<T,R>TR输入 T 返回 R,有输入也有输出
Supplier/T提供一个数据,没有输入只有输出
BiFunction<T,U,R>(T,U)R两个输入参数
BiPredicate<L, R>(L,R)boolean两个输入参数
BiConsumer<T, U>(T,U)void两个输入参数
BinaryOperator(T,T)T二元函数,输入输出类型相同

总结

对于一些常见的设计模式可以考虑使用Lambda表达式配合Java8内置的一些函数式接口,可以降低设计模式带来的一些代码臃肿的问题。这里再次强调一下,并不是所有场景都适合用Lambda优化的,如果程序的变化点执行逻辑相对简单可以考虑使用,但是有些处理逻辑比较复杂或者是有多个变化点时则不太适合。

参考

<Java 实战 第2版>