Scala设计模式

13-10-14 banq
Design Patterns in Scala一文列举了Java和Scala实现经典GoF设计模式的不同代码,有助于对比两种语言。

工厂模式:
Java代码:

public interface Animal {}

private class Dog implements Animal {}

private class Cat implements Animal {}

public class AnimalFactory {
    public static Animal createAnimal(String kind) {
        if ("cat".equals(kind)) return new Cat();
        if ("dog".equals(kind)) return new Dog();
        throw new IllegalArgumentException();
    }
}

AnimalFactory.createAnimal("dog");
<p class="indent">


Scala的代码:

trait Animal
private class Dog extends Animal
private class Cat extends Animal

object Animal {
  def apply(kind: String) = kind match {
    case "dog" => new Dog()
    case "cat" => new Cat()
  }
}

Animal("dog")
<p class="indent">


注意Scala使用了trait替代Java接口,使用Object替代Java的Class。

Lazy initialization懒初始化
懒初始化是懒赋值lazy evaluation的一种特殊实现。

Java代码:

private volatile Component component;

public Component getComponent() {
    Component result = component;
    if (result == null) {
        synchronized(this) {
            result = component;
            if (result == null) {
                component = result = new Component();
            }
        }
    }
    return result;
}
<p class="indent">


Scala代码:

lazy val x = {
  print("(computing x) ")
  42
}

print("x = ") 
println(x) 
<p class="indent">


Scala引入了lazy语法。

单态Singleton

Java代码:

public class Cat implements Runnable {
    private static final Cat instance = new Cat();
 
    private Cat() {}
 
    public void run() {
        // do nothing
    }

    public static Cat getInstance() {
        return instance;
    }
}

Cat.getInstance().run()
<p class="indent">


Scala代码:

object Cat extends Runnable {
  def run() {
    // do nothing
  }
}

Cat.run()
<p class="indent">


object语法其实是一个单例对象。

Adapter适配器

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());
<p class="indent">


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() 
<p class="indent">


Scala使用了语法implicit,隐式语法,它不是全局变量,也不是trait那样动态注入,隐藏魔法吧,在因果论中因有两种形式,显式因和隐式因,隐式因是无法显式看到的,所以new Logger还是之前原始类名,但是其实已经被调包。

Decorator适配器

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"))
<p class="indent">


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 
<p class="indent">

trait语法很适合进行方法拦截或添加。

banq
2013-10-14 08:56
值对象valueObject

值对象代表不可变,不变性,一旦构成就再也不改变。值对象是作为DTO的自然实现。

Java代码需要使用final

public class Point {
    private final int x, y;

    public Point(int x, int y) { this.x = x; this.y = y; }

    public int getX() { return x; }

    public int getY() { return y; }

    public boolean equals(Object o) {
        // ...
        return x == that.x && y == that.y;
    }

    public int hashCode() {
        return 31 * x + y;
    }

    public String toString() {
        return String.format("Point(%d, %d)", x, y);
    }
}

Point point = new Point(1, 2)
<p class="indent">


Scala主要针对可变和不可变提供var和val两个声明:
val point = (1, 2)


type Point = (Int, Int) // Tuple2[Int, Int]
val point: Point = (1, 2)


case class Point(x: Int, y: Int)
val point = Point(1, 2)

Null Object空对象
空对象代表一个对象什么也不做。

Java代码:

public interface Sound {
    void play();
}

public class Music implements Sound {
    public void play() { /* ... */ }
}

public class NullSound implements Sound {
    public void play() {}
}
public class SoundSource {
    public static Sound getSound() {
    	return available ? music : new NullSound();
    }
}
SoundSource.getSound().play();
<p class="indent">

使用空对象就不必检查getSound()是否为空,

scala代码:

trait Sound {
  def play()
} 
  
class Music extends Sound {
    def play() { /* ... */ }
}

object SoundSource {
  def getSound: Option[Sound] = 
    if (available) Some(music) else None
}
  
for (sound <- SoundSource.getSound) {
  sound.play()
}
<p class="indent">

Scala使用Option。它可以用来作为一个可选值的占位符。


Strategy策略模式
Java代码:

public interface Strategy {
    int compute(int a, int b);
}

public class Add implements Strategy {
    public int compute(int a, int b) { return a + b; }
}

public class Multiply implements Strategy {
    public int compute(int a, int b) { return a * b; }
}

public class Context  {
    private final Strategy strategy;

    public Context(Strategy strategy) { this.strategy = strategy; }

    public void use(int a, int b) { strategy.compute(a, b); }
}

new Context(new Multiply()).use(2, 3);
<p class="indent">


Scala利用其函数语言的魔力简化了这些方法:

type Strategy = (Int, Int) => Int 

class Context(computer: Strategy) {
  def use(a: Int, b: Int)  { computer(a, b) }
}

val add: Strategy = _ + _
val multiply: Strategy = _ * _

new Context(multiply).use(2, 3)
<p class="indent">



Command命令模式

java代码:

public class PrintCommand implements Runnable {
    private final String s;

    PrintCommand(String s) { this.s = s; }

    public void run() {
        System.out.println(s);
    }
}

public class Invoker {
    private final List<Runnable> history = new ArrayList<>();

    void invoke(Runnable command) {
        command.run();
        history.add(command);
    }
}

Invoker invoker = new Invoker();
invoker.invoke(new PrintCommand("foo"));
invoker.invoke(new PrintCommand("bar"));
<p class="indent">


Scala使用by-name parameter 实现:

object Invoker {
  private var history: Seq[() => Unit] = Seq.empty

  def invoke(command: => Unit) { // by-name parameter
    command
    history :+= command _
  }
}

Invoker.invoke(println("foo"))
  
Invoker.invoke {
  println("bar 1")
  println("bar 2")
}
<p class="indent">

个人感觉,虽然很简单,大道至简,但是简单得有点像魔术,属于玄了。


Chain of responsibility职责链
Scala代码:

rait EventHandler {
  def handle(event: Event)
}

class DefaultHandler extends EventHandler {
  def handle(event: Event) { /* ... */ }
}

trait KeyboardHandler extends EventHandler {
  abstract override def handle(event: Event) {
    if (event.source == "keyboard") /* ... */ else super.handle(event)
  }
}

trait MouseHandler extends EventHandler {
  abstract override def handle(event: Event) {
    if (event.source == "mouse") /* ... */ else super.handle(event)
  }
}

new DefaultHandler with KeyboardHandler with MouseHandler
<p class="indent">

使用trait 和with语法替代了Java的"."


banq
2013-10-14 09:02
Dependency injection依赖注入

Java代码:

interface Repository {
    void save(User user);
}

class DatabaseRepository implements Repository { /* ... */ }

class UserService {
    private final Repository repository;

    UserService(Repository repository) {
        this.repository = repository;
    }

    void create(User user) {
        // ...
        repository.save(user);
    }
}

new UserService(new DatabaseRepository());
<p class="indent">


java实现注入需要依赖Spring或JEE之类框架容器,而Scala好像不像之前显得那么从容,这点在发明语言时怎么没有考虑呢?依赖注入大概是针对类的,而不是函数。

trait Repository {
  def save(user: User)
}

trait DatabaseRepository extends Repository { /* ... */ }

trait UserService { self: Repository => // requires Repository
  def create(user: User) {
    // ...
    save(user)
  }
}

new UserService with DatabaseRepository
<p class="indent">


更多见 Cake pattern :Real-World Scala: Dependency Injection (DI)

lshoo
2013-10-17 08:21
Scala本身的语言特性比Java要强大很多,很多Java的设计模式用Scala就不存在了。
例如DI,Scala的cake pattern, function currying,implicit等实现起来都委优雅。

[该贴被lshoo于2013-10-17 08:58修改过]