设计模式原则
开闭原则
指 对扩展开放,做修改关闭. 为了使程序扩展性好,易于维护和升级,技术上使用接口和抽象. 其实现的关键是抽象化.
里氏替换原则
指 任何基类出现的地方,子类一定可以出现. 该原则是继承复用的基石,只有当原生类能够替换掉基类,并且软件单位的功能不受影响,基类才能真正被复用,而衍生类也能在基类的基础上增加新的行为.
是对开闭原则的补充,基类与子类的继承关系就是对抽象化的补充.因此该原则是对是对具体实现抽象化的规范.
依赖倒转原则
是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体.
接口隔离原则
指 使用多个隔离的接口比使用单个接口要好.即降低类之间的耦合度.
迪米特法则(最少知道原则)
一个实体之间尽量减少与其他实体之间发生交互作用,使得系统功能之间相对独立.
合成复用原则
尽量使用合成或聚合的方式而不使用继承.
创建型模式
抽象工厂
一种用于构造类型实例的抽象而不需要显式的指定类型.
object中的apply方法可以达到这个目的. 根据传入方法的参数创建合适类型的实例.
客户类和工厂类分开.消费者任何时候需要某种商品,只需向工厂请求即可.消费者无需修改就可以接纳新商品.缺点是当产品修改时,工厂类需要作出相应的修改,比如,如何创建或如何向客户端提供.
生成器
根据一个复杂对象的表达,将其构造进行拆分,从而使这种处理能够应用到多种不同的表达.
Scala中一个经典的例子是collection.generic.CanBuildFrom,从而是一些组合方法比如map,能够根据输入集合的类型创建新的集合.
即建造模式,将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象.建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节.建造模式可以强制实行一种分步骤进行的建造过程.
工厂方法
核心工厂类不再负责所有产品的创建,而是将具体的创建工作交给子类去做完成,称为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不应当接触哪一个产品类应当被实例化这种细节.
// 在子类中定义方法,重写或者实现实例化类型和如何进行实例化.
// CanBuildFrom.apply是一个用于构造能够创建实例创建器的抽象方法,子类或特定实例提供了详细方式. Applicative.apply提供了类似的抽象.
该模式将对实际的类的初始化封装在一个方法中,让子类来决定初始化那个类.
工厂方法允许:
- 组合发杂的 对象创建代码
- 选择需要初始化的类
- 缓存对象
- 协调对共享资源的访问
我们考虑静态工厂模式,这和经典的工厂模式略有不同,静态工厂方法避免了子类来覆盖此方法.
在Java中,我们使用new关键字,通过调用类的构造器来初始化对象.为了实现这个模式,我们需要依靠普通方法,此外我们不能在接口中定义静态方法,所以我们只能额外的使用一个工厂类.
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");
除了构造器外,Scala提供了一种类似于构造器调用的特殊语法,其实这就是一种简便的工厂方法:
trait Animal
private class Dog extends Animal
private class Cat extends Animal
object Animal {
def apply(kind: String) = kind match {
case "dog" => new
case "cat" => new }
}
Animal("dog")
在Scala代码中,工厂方法被定义为伴生对象,它是一种特殊的单例对象,和之前定义的类或特质拥有相同的名字,并且需要定义在同一个代码文件中.这种语法仅限于工厂模式中的静态工厂模式,因为我们不能讲创建对象的动作交给子类来完成.
Scala中的优势是重用基类名字,显现方法标准并且简洁,类似于构造器调用,但仅限于工厂方法.
延迟初始化模式
延迟初始化是一个延迟加载的特例.它指仅当第一次访问一个值或对象的时候,才会去初始化他们.
延迟初始化可以延迟或避免一些比较复杂的运算.
在Java中一般使用null来代表未初始化状态,但假如null是一个合法的final值的时候,我们就需要一个独立的标记来标识初始化过程已经进行.
在多线程环境中,对以上提到的标记的访问必须要进行同步,并且必须要进行双重检测技术(double-check),当然这也进一步增加了代码的复杂性:
private Volatile Componet componet;
public Componet getComponet() {
Componet result = componet;
if (result == null){
synchronized(this){
result = componet;
if (result == null){
componet = result = new Componet();
}
}
}
return result;
}
Scala中定义了一个内置语法来定义延迟变量:
lazy val x = {
print("(computing x) ")
42
}
print(s"x = $x")
// x = (computing x) 42
Scala中,延迟变量能够维持null值,并且是线程安全的.
Scala中的优势是语法简洁,延迟变量能够维持null值,并且线程安全,但是对初始化行为缺乏控制.
原型,原始模型
通过给出一个圆形对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象.原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,该模式适用于任何的等级结构,缺点是每一个类必须配备一个克隆方法,比如Scala中的copy.
从一个原型实例,添加一些可选的修改来构造一个新的实例.
case类的copy方法是一个非常棒的例子,用户可以指定需要修改的参数然后克隆一个新的实例.
单例
单例模式确保某一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例单例模式.单例模式只应该在有真正的”单一实例”的需求时才可使用.
Scala中提供了作为第一语言特性的objects.
单例模式限制了一个类只能初始化一个对象,并且会提供一个全局引用指向他.
在Java中,单例模式可能是最被人熟知的一个模式了.这是Java缺少语言特性的明显信号.
在Java中有static关键字,静态方法不能被任何对象访问,并且静态成员类不能实现任何接口.所以静态方法和Java提出的一切皆对象背离了.静态成员也只有个花哨的名字,本质上只不过是传统意义上的子程序.
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()
而Scala中单例模式非常简单:
object Cat extends Runnable {
def run() {
// do nothing
}
}
Cat.run()
Scala中的优势是含义明确语法简洁,能够按需初始化并且线程安全,但对初始化行为缺乏控制.
结构型模式
适配器
把一个类的接口变换成客户端所期待的另一种接口,从而使原本因为接口不匹配而无法一起工作的两个类能够一起工作.适配类能够根据参数返回一个合适的实例给客户端.
围绕一个抽象创建一个客户端预期的接口,然后后来的客户端就能够继续使用该接口.
适配器模式能将不兼容的接口放在一起协同工作,适配器对集成已存在的各个组件非常有用.
在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 warning(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编译器会自动把Log实例封装到适配器类中.
桥接
将抽象画与实现化脱耦,使得二者能够独立的变化,也就是说将二者的强关联编程弱关联.也就是指,在一个软件系统的抽象化和实现化之间使用组合/聚合关系,而不是继承关系,从而使得两者可以独立变化.
组合,合成
合成模式将对象组织到树结构中,可以用来描述整体和部分的关系.合成模式就是一个处理对象的树结构的模式.合成模式把部分与整体的关系用树结构表示出来.合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待.
装饰器
装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一种替代方案,提供比继承更多的灵活性.动态给一个对象增加功能,这些功能可以再动态的撤销.增加由一些基本功能的排列组合而产生的非常大量的功能.
装饰器模式被用来在不影响一个类的其他实例的基础上扩展一些对象的功能,是对继承的一个灵活替代.
当需要有很多独立的方式来扩展功能时,装饰器模式是很有用的,这些扩展可以随意组合.
在Java中,需要新建一个装饰类,实现原来的接口,封装原来实现接口的类,不同的装饰者可以组合使用.一个处于中间层的装饰者一般会用来代理原接口中很多方法.
Java实现:
public interface OutputStream {
void write(byte b);
void write(byre[] 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(b) {
// ....
delegate.write(buffer);
}
}
new BufferedOutputStream(new FileOutputStream("foo.txt"))
Scala提供了一种更直接的方式来重写接口中的方法,并且不用绑定到具体实现,下面看下如何使用override abstract 标示符:
trait OutputStream {
def write(b:Byte)
def write(b:Array[Byte])
}
class OutputStream(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...
这种代理是在编译时期静态建立的,不过通常来说只要我们能在创建对象时任何组合装饰器,就已经够用了.
与 基于组合(指需要特定的装饰类来把原类封装进去)的实现方式不一样.Scala保持了对象的一致性,所以可以在装饰器对象上放心使用equals.
Scala中的优势是含义清晰语法简单,保持了对象的一致性,无需显式代理,无需中间层的装饰类.但是其静态绑定,没有构造器参数.
值对象
值对象是一个很小的不可变对象,他们的相等性不急于identity(身份),而是基于不同对象包含的字段是否相等.
值对象被广泛应用于表示时间,数字,颜色等,在企业级应用中,他们常常被用做DTO(可以用来做进程间通信),由于不变性,值对象在多线程环境下使用非常方便.
Java中并没有特殊语法支持值对象,所以我们必须显式定义一个构造器,getter方法以及辅助方法.
Java实现:
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 == this.x && y == this.y;
}
public int hashCode() {
return x * 31 + y;
}
public String toString() {
return String.format("Point(%d, %d)", x, y);
}
}
Point point = new Point(1, 2)
在Scala中,我们使用元组或case类来申明值对象.当不需要使用特定的类或程序比较小的时候,元组就够了:
val point = (1, 2) // new Tuple2(1, 2)
元组是一个预先定义好的不变集合,它能够持有若干个不同类型的元素,元组提供构造器,getter方法以及所有辅助方法.
我们也可以给Point类定义一个别名:
type Point = (Int, Int)
// Tuple2[Int, Int] val point: Point = (1, 2)
当需要一个特定的类或者需要对数据元素名称有更明确的描述的时候,可以使用case类:
case class Point(x: Int, y: Int)
val point = Point(1, 2)
case类将构造器参数默认为属性,样例类是不可变的,与元组一样,它提供了所有所需的方法,因为样例类是合法的类,所以他也可以使用类继承及定义成员.
值对象模式是函数式编程中一个非常常用的工具,Scala在语言级别对其提供了支持.
Scala中的优势是语法简明,预定义元组类,内置了所有需要的辅助方法.
空值模式
空值模式定义了一个啥都不干的行为,这个模式比起空引用有一个优势,它不需要再使用前检查引用的合法性.
在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();
所以getSound获得Sound实例在调用play方法,不需要检查Sound实例是否为空.更进一步,我们可以使用单例模式来限制只生成唯一的空对象.
Scala也采用了类似的方法,但是他提供了一个Option类型,可以用来表示可有可无的值:
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()
}
在此场景下,我们可以使用for推导来处理Option类型,或者使用高阶函数或模式匹配.
Scala中的优势是预定义类型,明确的可选择性,内置结构支持.但是用法过于冗长.
外观,门面
外部与一个子系统的通信必须通过一个统一的门面对象进行.门面模式提供一个高层次的接口,使得子系统更易于使用.使得子系统更易于使用.每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说他是一个单例模式.但整个系统可以有多个门面类.
享元
享元模式以共享的方式高效的支持大量细粒度对象.享元模式能够做到共享的关键是区分内蕴状态和外蕴状态.内蕴状态存储在享元内部,不会随时间的改变而有所不同.外蕴状态是随时间的改变而改变的.外蕴状态不能影响内蕴状态,他们是相互独立的.
将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里提出出去.客户端不可以创建被共享的对象,而应当使用一个工厂对象创建被共享的对象.享元模式大幅度的降低内存中的对象数量.
代理
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用.代理就是一个人或一个机构代表另一个人或机构采取行动.
某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象之间起到中介的作用.客户端分辨不出代理主题对象和真是主题对象.代理模式可以并不知道真正的被代理对象,而仅仅持有被代理对象的一个接口,这时候代理对象不能够创建被代理对象,被代理对象只能够被系统的其他角色创建并传入.
QAZ EDCWSXEDCRFVTGBYHN UUJMIK,IK,LOL.OOPPOPOIOPOPOPIIO
ABCDREGFGHIHGKLMNOPQRDSTIUVWXYZ
行为模式
职责链
在责任连模式中,很多对象由每一个对象对其下家的引用而接起来形成一条链.请求在这个链上传递,知道链上的某一对象决定处理该请求.系统可以在不影响客户端的情况下动态的重新组织链和分配职责.处理者有两个选择:承担责任或把责任推给下家.一个请求可以不被任何接收端对象所接收.
职责链模式解耦了发送方与接收方,使得更多的对象有机会去处理这个请求,这个请求一直在这个链中流动直到一个对象处理了他.
职责链模式的一个典型实现就是职责链中的所有对象都会继承一个基类,并且可能会包含一个指向链中下一个处理对象的引用.每一个对象都有机会处理请求或中断请求,或者将请求推给下一个处理对象.职责链的顺序逻辑可以要么代理给对象处理,要么就封装在一个基类中.
public abstract class EventHandler {
private EventHandler next;
void setNext(EventHandler handler) { next = handler;}
public void handle(Event event){
if(canHandle(event)) doHandle(event);
else if (next != null) next.handle(event);
}
abstract protected boolean canHandle(Event event);
abstract protected void doHandle(Event event);
}
public class KeyboardHandler extends EventHandler { // MouseHandler
protected boolean canHandle(Event event){
return "keyboard".equals(event.getSource());
}
protected void doHandle(Event event) { /*..../}
}
KeyboardHandler handler = new KeyboardHandler();
handler.setNext(new MouseHandler());
由于以上的实现有点类似于装饰器模式,所有我们可以在Scala中使用abstract override来解决这个问题.不过Scala提供了一种更直接的方式,即偏函数.
偏函数简单来说就是某个函数只会针对他参数的可能值的自己进行处理.可以直接使用偏函数的isDefinedAt 和apply方法来实现顺序逻辑.更好的方法是使用内置的orElse方法来实现请求的传递.
case class Event(source:String)
type EventHandler = PartialFunction[Event, Unit]
val defaultHandler:EventHandler = PartialFunction(_ => ())
val keyboardHandler:EventHandler = {
case Event("keyboard") => /*..../
}
def mouseHandler(delay:Int):EventHandler = {
case Event("mouse") => /*..../
}
keyboardHandler.orElse(mouseHandler(100)).orElse(defaultHandler)
注意在整个流的最后我们必须使用defaultHandler来避免出现”undefined”时间的错误.
Scala中的优势是语法简洁内置逻辑,但是是通用类型.
命令
命令模式把一个请求或操作封装到一个对象中.命令模式把发出命令的的责任和执行命令的责任分开,委派给不同的对象.命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收的一方的接口,更不必知道请求是怎么被接收的,以及操作是否执行,合适被执行以及怎么被执行的,系统支持命令的撤销.
命令模式封装了需要在稍后调用方法的搜有信息,这些信息包括拥有这些方法的对象和这些方法的参数值.
命令模式适合于延迟方法调用,顺序花方法调用以及方法调用时记录日志等等.
在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> histroy = 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"));
在Scala中,我们使用换名调用来实现延迟调用:
object Invoker {
private var history:Seq[() => Unit] = Seq.empty
def invoke(command: => Unit){ // by name parameter
command
histoty :+ = command _
}
}
Invoker.invoke(println("foo"))
Invoker.invoke {
println("bar1")
println("bar2")
}
这就是我们怎么把任意的表达式或者代码块转换为一个函数对象.当调用invoke方法的时候才会调用println方法,然后以函数形式存在历史序列中.我们也可以直接定义函数,而不采用换名调用,但是那种方法太冗长了.
解释器
给定一个语言后,解释器可以定义出其文法的一种表示,并同时停工一个解释器.客户端可以通过这个解释器解释这个语言中的句子.解释器模式将描述,怎样在有了一个简单的文法后,使用模式设计解释这些语句.在解释器模式里提到的语言是指任何解释器对象能够解释的的任何组合.在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则.每一个命令对象都有一个解释方法,代表对命令对象的解释.命令对象的等级结构中的对象的任何排列组合都是一个语言.
迭代器,迭代子
迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集中的内部表象.多个对象聚集在一起形成的整体称之为聚集,聚集对象是能够包容一组对象的容器对象.迭代子模式将迭代逻辑封装到一个独立的对象中,从而与聚集本身隔开.迭代子模式简化了聚集的界面.每一个聚集对象都可以有一个或多个迭代子对象,每一个迭代子的迭代状态都可以是独立的.迭代算法可以独立于聚集角色变化.
中介,调停者
调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用.从而使他们可以松散耦合.当某些对象之间的作用发生变化时,不会立即影响其他对象之间的相互作用.保证这些作用可以彼此独立的变化.调停者模式将多对多的相互作用转化为一对多的相互作用.调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他独享的相互作用分开处理.
备忘录
备忘录对象是一个用来存储另一个对象内部状态快照的对象.备忘录模式的用意是在不破坏封装的条件下,捕捉一个对象的状态,并外部化,存储起来,从而可以在将来合适的时候可以把这个对象还原到存储起来的状态.
观察者
管擦者模式定义了一种一对多的依赖关系,让多个管擦着对象同时监听一个主题对象.这个主题对象在状态发生变化时,会通知所有管擦着对象,使他们能够自动更新自己.
状态
状态模式允许一个对象在其状态改变时改变行为.这个对象看上去像是改变了自身的类一样.状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类.状态模式的意图是让一个对象内部状态发生改变时,其行为也随之改变.状态模式需要对每一个系统可能取得的状态创立一个状态类的子类,当系统的状态变化时,系统便改变所选的子类.
策略
策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的子类中,从而使他们可以相互替换.策略模式使得算法可以在不影响客户端的情况下发生改变.策略模式把行为和环境分开.环境类负责维持和查询行为类,各种算法在具体的策略类中提供.由于算法和环境独立开来,算法的增减修改都不会影响到环境和客户端.
策略模式定义了一组封装好的算法,让算法变换独立于用户的调用,需要在运行时选择算法时,策略模式非常有用.
在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);
而在Scala中,函数是第一公民,可以直接实现如下:
type Stagtegy = (Int, Int) => Int
class Context(computer:Stagtegy) {
def use(a:Int, b:Int) { computer(a, b)}
}
val add:Stagtegy = _ + _
val multiply:Stagtegy = _ * _
new Context(multiply).use(2, 3)
加入策略包含很多方法的话,我们可以使用元组或样例类把所有方法封装在一起.
模板方法
模板方法模式准备一个抽象类,将部分逻辑以具体方法和具体构造子的形式实现,然后声明一些抽象方法使得子类实现剩余的逻辑.不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现.先制定一个顶级逻辑框架,而将逻辑的细节留个具体的子类去实现.
访问者
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要修改的话,接收这个操作的数据结构可以保持不变.访问者模式适用于数据结构相对未定的系统,把数据结构和作用于数据结构之上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化.访问这模式使得增加新的操作变得容易,就是增加一个新的访问者类.访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个节点类中.当使用访问者模式时,要将尽量可能多的对象浏览逻辑放在访问者类中,而不是放到他的子类中.访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类.
依赖注入模式
依赖注入可以让我们避免硬编码依赖关系,并且在编译器或运行时替换依赖关系,此模式是控制翻转的一个特例. 框架Spring中使用较多.
依赖注入是在某个组件的众多实现中选择,或者为了单元测试而去模拟组件.
除了IoC容器,在Java中最简单的实现就是像构造器参数需要的依赖,所以我们利用组合来表达依赖需求.
public interface Repository {
void Save(User user);
}
public class DatabaseRespository implements Respository { /*..../ }
public class UserService {
private final Respository respository;
UserService(Respository respository){
this.respository = respository;
}
void create(User user){
// ....
respository.save(user);
}
}
new UserService(new DatabaseRespository());
除了组合HAS-A与集成HAS-A的关系外,Scala还增加了一种关系:需要 REQUIRES-A,通过自身类型注解来实现.
Scala中可以混合使用自身类型与特质来进行依赖注入:
trait Respository{
def save(User user)
}
trait DatabaseRespository extends Respository { /*..../ }
trait UserService { self: Respository =>
def create(user:User){
// ....
save(user)
}
}
new UserService with DatabaseRespository
不同于构造器注入,以上方式有个要求:配置中每一中依赖都需要一个单独的引用,这种技术的完整实现就叫蛋糕模式,当然Scala中还有很多其他方式来实现依赖注入.
在Scala中,既然混入的特质是静态的,所以此方法也仅限于编译期依赖注入,事实上,运行时的依赖注入几乎用不着,而对配置的静态检查相对于运行时检查有很大的优势.