这篇文章主要是翻译Design Patterns in Scala,但有所缩减和改动。
适配器模式(Adapter)
适配器模式(adapter pattern )是将一个类型的接口转换成程序需要的接口,这样可以解决接口不兼容的问题,通过转换可以让原来不兼容接口在一起工作。适配器模式主要用来和已有组件做集成。
Java 通过一个包装类( wrapper class)来实现这个模式, 这种做法很普遍,下面是Java代码:
public interface Log {
void warning(String message);
void error(String message);
}
public final class Logger {
void log(Level level, String message) { /* ... */ }
}
public class LoggerToLogAdapter implements Log {
private final Logger logger;
public LoggerToLogAdapter(Logger logger) { this.logger = logger; }
public void warning(String message) {
logger.log(WARNING, message);
}
public void error(String message) {
logger.log(ERROR, message);
}
}
Log log = new LoggerToLogAdapter(new Logger());
对于scala,我们有一个内建的接口适配器概念,叫做隐式转换,或者隐式类。
trait Log {
def warning(message: String)
def error(message: String)
}
final class Logger {
def log(level: Level, message: String) { /* ... */ }
}
implicit class LoggerToLogAdapter(logger: Logger) extends Log {
def warning(message: String) { logger.log(WARNING, message) }
def error(message: String) { logger.log(ERROR, message) }
}
val log: Log = new Logger()
当我们的期望值是一个 Log 类型的时候,我们可以用一个 Logger 实例,这时scala编译器会自动将 Logger 类型转换成 Log 的包装类型 LoggerToLogAdapter。
优势:
- 内容清晰。
- 语法简洁。
劣势:
- 如果没有IDE的帮助代码可能不太好理解。
装饰器(Decorator)
装饰器模式(decorator pattern)用来扩展已有对象的一些功能,并且不影响使用这个类型的其它实例。装饰器模式可以灵活的替代子类,它在扩展已有功能方面十分有用,可以与其它的设计模式很好的结合使用。
用Java实现,我们一般会定义一个新的装饰器类来继承基类接口并且包装原来的类,这样多个装饰器可以一个落一个的叠在一起使用。我们还可以使用一个中间装饰器作为代理来抽象出方法。见下面的代码:
public interface OutputStream {
void write(byte b);
void write(byte[] b);
}
public class FileOutputStream implements OutputStream { /* ... */ }
public abstract class OutputStreamDecorator implements OutputStream {
protected final OutputStream delegate;
protected OutputStreamDecorator(OutputStream delegate) {
this.delegate = delegate;
}
public void write(byte b) { delegate.write(b); }
public void write(byte[] b) { delegate.write(b); }
}
public class BufferedOutputStream extends OutputStreamDecorator {
public BufferedOutputStream(OutputStream delegate) {
super(delegate);
}
public void write(byte b) {
// ...
delegate.write(buffer)
}
}
new BufferedOutputStream(new FileOutputStream("foo.txt"))
Scala提供了一个直接的方式来重载接口方法,而不用直接绑定到具体的实现。
trait OutputStream {
def write(b: Byte)
def write(b: Array[Byte])
}
class FileOutputStream(path: String) extends OutputStream { /* ... */ }
trait Buffering extends OutputStream {
abstract override def write(b: Byte) {
// ...
super.write(buffer)
}
}
new FileOutputStream("foo.txt") with Buffering // with Filtering, ...
这种代理(trait Buffering...)是在编译的时候静态建立的,但是我们可以在对象创建的时候去任意组合它们。作为基于组件方式实现(上面java的实现)的对立面,scala的实现方式很好的保护了对象的独立性,这样我们可以在被装饰的对象上安全的使用 “equals” 等方法。
在Scala中,这样使用trait的方式被称为Stackable Trait Pattern。
优势:
- 内容清晰。
- 语法简洁。
- 保护了对象的独立性。
- 不用清晰的代理。
- 不用中间装饰器。
劣势:
- 静态绑定。
- 没有构造参数。