适配器(继承)

设计模式 / 结构型模式
image

use std::ops::{Deref, DerefMut};

// Target
trait Text {
    fn get_text(&self) -> String; // Request()
}

// Adaptee
#[derive(Default)]
struct StringList { rows: Vec<String> }

impl StringList {
    fn add(&mut self, value: &str) {
        self.rows.push(value.to_string());
    }

    fn get_string(&self) -> String {
        self.rows.join("\n") // SpecificRequest()
    }
}

// Adapter: wraps Adaptee, "inherits" its methods via Deref.
// NOTE: Deref-based "inheritance" is a known anti-pattern in idiomatic Rust;
// shown here only to mirror class-based Adapter from OOP languages.
struct TextAdapter(StringList);

impl Deref for TextAdapter {
    type Target = StringList;
    fn deref(&self) -> &StringList { &self.0 }
}

impl DerefMut for TextAdapter {
    // needed for add()'s &mut self
    fn deref_mut(&mut self) -> &mut StringList { &mut self.0 } 
}

impl Text for TextAdapter {
    fn get_text(&self) -> String {
        // Request() delegates to SpecificRequest()
        self.get_string() 
    }
}

fn main() {
    let mut adapter = TextAdapter(StringList::default());
    adapter.add("line 1"); 
    adapter.add("line 2");
    //Output: line 1
    //        line 2 
    println!("{}", adapter.get_text());
}


Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
Read more

问题。程序通过一个方法从任意来源读取文本。某个现成的库类保存若干行文本,也能把它们拼接起来,但用的是另一个名字的方法。这个库无法修改。调用程序所期望方法的全部代码,也不想改动。

思路。适配器让一个已有的类,即被适配者,符合客户端所期望的接口,即目标接口。在这个版本中,适配器是被适配者的子类,同时实现目标接口。它的目标方法只是调用继承来的方法,有时做一点小小的转换。因此适配器本身就是一个被适配者,保留了它的全部方法。可以与组合方式的适配器对照:后者持有一个被适配者,而不是继承它。

如何阅读示例。被适配者已经在做这项工作,只是用的是另一个方法。目标接口声明了客户端所期望的方法。适配器继承被适配者并添加目标方法,由目标方法调用继承来的方法。对象只有一个:适配器就是被适配者。在大多数语言中,客户端通过被适配者自身的方法填充它,再通过目标方法读取结果。没有类继承的语言会模仿继承:把被适配者嵌入进来,或者把目标方法添加到被适配者自身的类型上。

何时使用。当要适配的是一个已知的类,并且还需要它的其他方法时,使用类适配器。如果要适配的对象已经存在,或者它可能属于被适配者的某个子类,就不要使用它。类适配器总是某个固定类的新对象,因此无法适配这样的对象。

常见错误。忘记适配器会暴露被适配者的每一个方法。客户端开始直接调用这些方法,目标接口也就不再能把被适配者隐藏起来。