暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

从函数式的角度重看GOF设计模式(二)

写程序的康德 2016-07-12
393

在第一部分中,我们一起回顾了常见的GoF设计模式然后用Java 8的语法重写了一个更加干净,简洁的实现。第二部分我们继续还是这样的研究方式,一起看一下两个广泛使用的GoF设计模式:Template和Observer,然后我们使用Java 8的Consumer接口重新实现它们。

Template(模板)模式

通过一个例子来看Template模式,我们定义一个Resource类

public class Resource {
    public Resource() {
        System.out.println("Resource created");
    }
    public void useResource() {
        riskyOperation();
        System.out.println("Resource used");
    }
    public void employResource() {
        riskyOperation();
        System.out.println("Resource employed");
    }
    public void dispose() {
        System.out.println("Resource disposed");
    }
    private void riskyOperation() {
        if ( new Random().nextInt( 10 ) == 0) {
            throw new RuntimeException();
        }
     }
 }

我们的Resource类有一个构造函数,一些使用资源的方法,这些方法可能会调用失败,比如是一个数据库或网络连接,失败之后会抛出RuntimeException。这里我们通过随机方式来模拟出错。无论成功与否,最后都是调用dispose来释放相关资源,比如内存、数据库连接、打开的文件。如果用如下方式使用这个类:

Resource resource = new Resource();
resource.useResource();
resource.employResource();
resource.dispose();

是错的,因为useResource、employResource可能会失败。当抛出异常的时候资源没有办法释放。所以正确的姿势应该是:

Resource resource = new Resource();
try {
    resource.useResource();
    resource.employResource();
} finally { 
    resource.dispose();
}

如果要避免资源泄露就必须强制API调用者遵循这种代码结构。换句话说,我们要保证所有资源操作都遵循这样的代码结构:

openResource();
try {
    doSomethingWithResource();
} finally {
   closeResource();
}

问题是,我们不能保证所有人调用的时候都遵循这种模式。所以我们需要一个抽象模板类,来封装这样的“代码结构”:

public abstract class AbstractResourceManipulatorTemplate {
    protected Resource resource;
    private void openResource() {
        resource = new Resource();
    }
    protected abstract void doSomethingWithResource();
    private void closeResource() {
        resource.dispose();
        resource = null;
    }
    public void execute() {
        openResource();
        try {
            doSomethingWithResource();
        } finally {
            closeResource();
        }
    }
}

这个抽象模板封装我们使用资源的方式,具体的对资源的操作我们在子类中实现(doSomethingWithResource)

public class ResourceUser extends AbstractResourceManipulatorTemplate {
    @Override
    protected void doSomethingWithResource() {
        resource.useResource();
    }
}
public class ResourceEmployer extends AbstractResourceManipulatorTemplate {
    @Override
    protected void doSomethingWithResource() {
        resource.employResource();
    }
}

API使用者只需要调用execute,根据不同的子类实现doSomethingWithResource使用资源。execute中的代码结构保证了资源最终肯定会被释放。

new ResourceUser().execute();
new ResourceEmployer().execute();

Template模式可以帮我们实现对资源的管理,并且我们看到了GoF所采用的实现有多么的长。。长。。长。如果是函数式思路来做,我们只需要定义一个接受Consumer接口作为参数的方法:

public static void withResource( Consumer<Resource> consumer) {
    Resource resource = new Resource();
    try {
        consumer.accept( resource );
    } finally {
        resource.dispose();
    }
}

注意这个方法相当于上面的execute方法,唯一的区别是模板的doSomethingWithResource的方法被Consumer参数代替了。子类中使用资源的业务逻辑被lambda表达式代替了:

withResource( resource -> resource.useResource() );
withResource( resource -> resource.employResource() );

Observer(观察者)模式

观察者模式是一个很常见、广泛使用的设计模式。它解决的问题是,当触发某个事件的时候可以通知一个或多个对象响应该事件。一般我们会定义一个Listener接口:

interface Listener {
    void onEvent(Object event);
}

当一个对象需要响应或者监听事件,只需要实现这个接口,然后在onEvent中编写处理事件的代码。与之对应的我们还需要定义一个Observable,它用来注册监听的事情,并且在事件到来之后把事件转发给的Listener。

public class Observable {
    private final Map<Object, Listener> listeners = new ConcurrentHashMap<>();
    public void register(Object key, Listener listener) {
        listeners.put(key, listener);
    }
    public void unregister(Object key) {
        listeners.remove(key);
    }
    public void sendEvent(Object event) {
        for (Listener listener : listeners.values()) {
            listener.onEvent( event );
        }
    }
}

我们可以用这种内部类的方式来实现Listener,注册自己感兴趣的事件:(注意Observer1这个时候不需要实现Listener接口)

public class Observer1 {
    Observer1(Observable observable) {
        observable.register( this, new Listener() {
            @Override
            public void onEvent( Object event ) {
                System.out.println(event);
            }
        } );
    }
}

可以直接通过实现Listener接口

public class Observer2 implements Listener {
    Observer2(Observable observable) {
        observable.register( this, this );
    }
    @Override
    public void onEvent( Object event ) {
        System.out.println(event);
    }
}

两个观察者的使用方式是一样的,当发送一个事件后会广播给所有的观察者:

Observable observable = new Observable();
new Observer1( observable );
new Observer2( observable );
observable.sendEvent( "Hello World!" );

和所有GoF模式犯的错误一样:把“函数”封装成一个对象(Java中的事件监听本质上就是一个函数),要么通过一个匿名类,要么单独实现一个类(这种类往往只有一个方法)。如果使用Java 8的特性,我们可以通过Consumer来替代Listener,在调用的时候直接传递“事件处理函数”,不用定义一大堆类。

Observable observable = new Observable();
observable.register( "key1", e -> System.out.println(e) );
observable.register( "key2", System.out::println );
observable.sendEvent( "Hello World!" );

在接下来的第三部分,我们继续尝试用函数式的方式来实现GoF模式中的——Decorator和Chain Of Responsibility模式。

文章转载自写程序的康德,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论