日历

October 2009
M T W T F S S
« Sep   Nov »
 1234
567891011
12131415161718
19202122232425
262728293031  

冒号课堂§10.1:多态类型

冒号课堂

第十课 多态机制(1)

课前导读

本课通过实例编程和对抽象类型的解读,显示了OOP中多态机制和抽象类型的重要性,有助于培养和加深读者的OOP语感。

本课共分两节——

1.多态类型——静中之动

2.抽象类型——实中之虚

 

10.1 多态类型——静中之动

郑晖

摘要

通过实例展示多态类型的三种用法


动静屈伸,唯变所适

《王弼•周易略例》

!预览

  • 继承是多态的基础,多态是继承的目的

  • 多态是动静结合的产物,将静态类型的安全性和动态类型的灵活性融为一体

  • 前者(参数多态)是发散式的,让相同的实现代码应用于不同的场合

  • 后者(包含多态)是收敛式的,让不同的实现代码应用于相同的场合

  • 模板方法模式突出的是稳定坚固的骨架,策略模式突出的是灵活多变的手腕

?提问

  • 多态与继承有何关系?

  • 多态的重要意义何在?

  • 多态有哪几种形式?它们各自有什么特点?

  • 什么是策略模式?它与模板方法模式有何相同点和不同点?多态在其中起到了什么作用?

:讲解

当冒号迈着不变的步伐出现在教室时,手上有了一点变化:左手仍拎着笔记本包,右手却多了一样东西。大家定睛一看,原来是个电脑主板,不由得暗自纳闷:难道软件课改成了硬件课?

冒号照例直入主题:“上节课我们对继承的利弊作了详细的分析,其中最重要的观点是:继承的主要用途不是代码重用,而是代码被重用。这依赖于两个前提,一个是在语义上遵循里氏代换原则,另一个是在语法上支持多态(polymorphism)机制。因此不妨说,对于静态类型语言来说,继承是多态的基础,多态是继承的目的。”

问号忍不住问:“为什么要强调静态类型呢?”

“还记得鸭子类型[1]吗?那就是一种不依赖于继承的多态类型,也是动态类型语言一大优劣参半的特色。”冒号提醒道,“静态类型语言中的多态是动静结合的产物,将静态类型的安全性和动态类型的灵活性融为一体。它一般有两种实现方式:一种利用GP(泛型编程)中的参数多态(parametric polymorphism),一种利用OOP中的包含多态(inclusion polymorphism)或称子类型多态(subtyping polymorphism)。从实现机制上看,二者的不同之处在于何时将一个变量与其实际类型所定义的行为挂钩。前者在编译期,属于早绑定 (early binding)或静态绑定(static binding)[2];后者在运行期,属于迟绑定 (late binding)或动态绑定(dynamic binding)。从应用形式上看,前者是发散式的,让相同的实现代码应用于不同的场合;后者是收敛式的,让不同的实现代码应用于相同的场合。从思维方式上看,前者是泛型式编程风格,看重的是算法的普适性;后者是对象式编程风格,看重的是接口与实现的分离度。尽管二者从范式到语法、语义都大相径庭,但都是为着同一个目的:在保证必要的类型安全的前提下,突破编译期间过于严苛的类型限制。对于既是静态类型语言又是静态语言、既支持OOP又支持GP的C++、Java和C#而言,多态机制是保证代码的灵活性、可维护性和可重用性的终极武器。为了说明问题,我们看一个简单而实用的例子:编写一个类,让它能储存用户名和密码,以作今后验证之用。”

叹号一愣:“这题是不是太简单了?还有别的要求吗?”

冒号摇摇头。

引号却认为:“要求太少反而不好做。比如是把数据放在内存、还是文件或者数据库?密码以明文还是密文的形式存储?”

句号提出:“无论是数据的存放方式还是密码的加密方式,都不应该硬编码。”

“循此思路,我们就来编写一个可重用的抽象类。”冒号投放了一段Java代码——

/** 一个可以验证用户名和密码的类  */
abstract class Authenticator
{
    /** 保存用户名和密码  */
    final public void save(String user, String password)
    {
        if (password == null)
            password = "";
        store(user, encrypt(password));
    }

    /** 验证用户名和密码  */
    final public boolean authenticate(String user, String password)
    {
        String storedPassword = retrieve(user);
        if (storedPassword == null) return false; // 无此用户
            
        if (password == null)
            password = "";
        return storedPassword.equals(encrypt(password));
    }

    /** 保存用户名和加密过的密码  */
    protected abstract void store(String user, String encryptedPassword);

    /** 从用户名获取相应的加密过的密码  */
    protected abstract String retrieve(String user);

    /** 给明文单向(one-way)加密,默认不加密  */
    protected String encrypt(String text) { return text; }
}

冒号解说道:“该抽象类有两个public接口,一个用来保存,一个用来验证。它们用final修饰符来禁止子类覆盖,因为真正的扩展点是三个protected方法。其中store和retrieve是抽象的,encrypt有一个平凡实现。以此为基础,再根据实际需要来编写子类,具体实现这三个方法。”

幻灯片转到下一页——

import java.util.Map;
import java.util.HashMap;

/** 一个简单的验证类,数据放在内存,密码保持明文  */
class SimpleAuthenticator extends Authenticator
{
    private Map<String, String> usrPwd = new HashMap<String, String>();

    @Override protected void store(String user, String encryptedPassword)
    {
        usrPwd.put(user, encryptedPassword);  
    }

    @Override protected String retrieve(String user)
    {
        return usrPwd.get(user);  
    }
}

“我们利用HashMap来储存数据,密码保持明文。这大概是最简单的一种子类了。”冒号仿佛在轻轻地把玩着一件小物什,“为安全起见,最好还是将密码加密。于是我们设计了稍微复杂一点的子类——”

import java.security.MessageDigest;

/** 一个安全的验证类,数据放在内存,密码经过SHA-1加密  */
class Sha1Authenticator extends SimpleAuthenticator
{
    private static final String ALGORITHM = "SHA-1"; // SHA-1算法
    private static final String CHARSET = "UTF-8"; // 避免依赖平台

    @Override protected String encrypt(String plainText)
    {
        try
        {
            MessageDigest md = MessageDigest.getInstance(ALGORITHM);
            md.update(plainText.getBytes(CHARSET));
            byte digest[] = md.digest();
            // BASE64编码比十六进制编码节省空间
           //为简便起见用到了非标准的API,因此以下代码有警告 
            return (new sun.misc.BASE64Encoder()).encode(digest); 
        }
        catch (java.security.NoSuchAlgorithmException e)
        {
            throw new InternalError(e.getMessage());  // 不可能发生
        }
        catch (java.io.UnsupportedEncodingException e)
        {
            throw new InternalError(e.getMessage());  // 不可能发生
        }
    }
}

逗号质疑道:“不是具体类不宜被继承的吗?怎么Sha1Authenticator类却继承了具体类SimpleAuthenticator?”

冒号略表赞许:“很高兴你没有忘记这个原则。不过考虑到Sha1Authenticator类需要覆盖父类的encrypt方法,这么做也是情有可原的。当然最好选择让该类直接继承抽象类Authenticator,但作为示例代码,我们还是希望它简洁一些,不想让过多的细枝末节掩盖核心主干。下面是测试代码——”

public class TestAuthenticator 
{  // 为避免额外依赖,没有采用JUnit等单元测试工具
    public static void main(String[] args)
    {
        test(new SimpleAuthenticator());
        test(new Sha1Authenticator());
    }

    // 测试给定的Authenticator 
    private static void test(Authenticator authenticator) // 子类型多态
    {
        test(authenticator, "user", "password");
        test(authenticator, "user", "newPassword");
        test(authenticator, "admin", "admin");
        test(authenticator, "guest", null);
        test(authenticator, null, "pass");

        authenticator.save("scott", "tiger");
        assert(!authenticator.authenticate("scott", "TIGER")); // 大小写敏感
        assert(!authenticator.authenticate("SCOTT", "tiger")); // 大小写敏感
    }

    private static void test(Authenticator authenticator, String user, String password)
    {
        authenticator.save(user, password);
        assert(authenticator.authenticate(user, password));
    }
}

引号觉得眼熟:“这不是上节课讲的模板方法模式吗?”

“正是此公。”冒号确认,“该模式的核心思想是:固定整体框架和流程以保证可重用性,留出一些子类定制点以保证可扩展性。在测试代码的两个test方法中,传入的参数是Authenticator类,但数据存放和密码加密的方式是在运行中才确定的,即先后遵照SimpleAuthenticator类和Sha1Authenticator类的实现。这就是我们所说的子类型多态的效果——让不同的实现代码应用于相同的场合。假设没有多态机制,这种效果就只能靠if/else或switch之类的条件语句才能实现,非常地痛苦。”

冒号的眉头皱成了粗体的“川”字。

“还有更好的方法吗?”句号察言观色,断定老冒还留有后手。

果不其然,冒号的眉毛立刻又舒展开来,中气充沛地应道:“有!诸位请看——”

// 键值对的存取接口
interface KeyValueKeeper
{
    public void store(String key, String value);
    public String retrieve(String key);
}

// 加密接口
interface Encrypter
{
    public String encrypt(String plainText);
}

class Authenticator
{
    private KeyValueKeeper keeper;
    private Encrypter encrypter;

    public Authenticator(KeyValueKeeper keeper, Encrypter encrypter)
    {
        this.keeper = keeper;
        this.encrypter = encrypter;
    }

    public void save(String user, String password)
    {
        if (password == null)
            password = "";
        keeper.store(user, encrypter.encrypt(password));
    }

    public boolean authenticate(String user, String password)
    {
        String storedPassword = keeper.retrieve(user);
        if (storedPassword == null) return false;

        if (password == null)
            password = "";
        return storedPassword.equals(encrypter.encrypt(password));
    }
}

冒号加以引导:“如果仔细比较两种设计,就会发现它们很相似。后者只不过把前者对子类开放的接口合成为自己的两个成员。再看接口的实现类——”

class MemoryKeeper implements KeyValueKeeper
{
     private Map<String, String> keyValue = new HashMap<String, String>();

    @Override public void store(String key, String value)
    {
        keyValue.put(key, value);  
    }

    @Override public String retrieve(String key)
    {
        return keyValue.get(key);  
    }
}

class PlainEncrypter implements Encrypter
{
    @Override public String encrypt(String plainText)
    {
        return plainText;
    }
}

class Sha1Encrypter implements Encrypter
{
    private static final String ALGORITHM = "SHA-1";
    private static final String CHARSET = "UTF-8"; 

    @Override public String encrypt(String plainText)
    {
        try
        {
            MessageDigest md = MessageDigest.getInstance(ALGORITHM);
            md.update(plainText.getBytes(CHARSET));
            byte digest[] = md.digest();
            return (new sun.misc.BASE64Encoder()).encode(digest); 
        }
        catch (java.security.NoSuchAlgorithmException e)
        {
            throw new InternalError(e.getMessage());
        }
        catch (java.io.UnsupportedEncodingException e)
        {
            throw new InternalError(e.getMessage());
        }
    }
}

逗号比较后得出结论:“MemoryKeeper与SimpleAuthenticator、Sha1Encrypter与Sha1Authenticator除了超类型和方法访问修饰符外,其他毫无二致。”

屏幕滚动出另一段代码——

public class TestAuthenticator
{
    public static void main(String[] args)
    {
        test(new Authenticator(new MemoryKeeper(), new PlainEncrypter()));
        test(new Authenticator(new MemoryKeeper(), new Sha1Encrypter()));
    }	

    private static void test(Authenticator authenticator) // 隐含子类型多态
    { /* 同上,略 */}
}

“测试代码区别也不大,只是Authenticator的多态性更加隐蔽。”冒号如是说。

叹号挑剔说:“后一种创建实例稍显麻烦一些。”

“但它是以小弊换大利。”冒号朗声而道,“首先,后者用的是合成与接口继承,比前者的实现继承更值得推荐,理由在上堂课业已阐明。其次,假设共有M种数据存取方式,包括内存、文件、数据库等等;共有N种加密方式,包括明文、SHA-1、SHA-256、MD5等等。按第一种设计,需要(M×N)个实现类;按第二种设计,只要(M+N)个实现类。这还只是两种变化因素,假如需要考虑更多的因素,二者差距将更大。比如增加编码方式:加密后的数据可以选择费空间省时间的十六进制编码、费时间省空间的BASE64编码、省时间省空间却包含非打印字符的原始形式等;比如增加安全强度:引入salt、nonce或IV等[3];比如增加密码状态:已生效密码、未生效密码、已过期密码等等。对比下面的UML类图,孰优孰劣更加一目了然。”

众人眼前出现了两幅图——

图10-1. Authenticator的UML类图(模板方法模式)

Authenticator的UML类图(模板方法模式)

图10-2. Authenticator的UML类图(策略模式)

Authenticator的UML类图(策略模式)

冒号指着屏幕问:“图二不仅比图一少了三个实现类,而且可重用性也更高。大家说是为什么?”

引号应答:“图一中的九个Authenticator的子类只能作为验证类来重用,而图二中六个实现类不仅可以合作完成验证类的功能,还能分别单独提供键值存储和加密字符串的功能。”

冒号作出肯定:“这就是职责分离的好处。存储与加密本是两样不相干的工作,必要时可以合作,但平时最好分开管理,符合‘低耦合、高内聚’的原则。”

问号注意到图中的注释,遂问:“第二种采用的是策略模式?”

冒号颔首:“简单地说,策略模式(strategy pattern或policy pattern)的基本思想是:把一个模块所依赖的某类算法委交其他模块实现。比如Java中的Comparable和Comparator、C#中的IComparer就是比较算法的接口,当一个类的某个方法接收了此种类型的参数,实质上就采用了策略模式。”

逗号不以为奇:“这岂非很平常?”

“你认为设计模式真的高不可攀吗?”冒号反问道,“包括模板方法模式,你们很可能也在编程实践中采用过,只不过相交不相识罢了。”

句号看出:“模板方法模式与策略模式非常神似,都是把一个类的可变部分移交给其他类处理。”

“照你这么说,绝大多数设计模式都是神似的,这也是为什么我们不专门谈设计模式的缘故。GoF设计模式是OOP大树上结出的硕果,在你心中培养的OOP成熟之前,匆忙缔结的果实多半是青涩弱小的。”冒号忠告,“我们也不会对设计模式避而不谈,但凡提及都是水到渠成的产物。再说回这两种设计模式,虽然有相通的思想,也能解决相同的问题,在稳定性与灵活性之间都取得了某种平衡,但还是各有侧重的。模板方法模式突出的是稳定坚固的骨架,策略模式突出的是灵活多变的手腕。不妨拿国家政策作比:一个强调对内要稳,老一辈制订了大政方针,下一代必须在坚持原则的前提下进行完善;一个强调对外要活,不能或不便自行开发的技术不妨从国外引进。”

叹号一乐:“哈!设计模式上升到了政策模式。”

冒号抽丝剥茧:“正如模板方法模式可看作控制反转的特例,策略模式与依赖注射(Dependency Injection)也异曲同工。第二个Authenticator所依赖的两个功能KeyValueKeeper和Encrypter,就是是通过构造方法‘注射’进来的[4]。当然策略只是一种特殊的依赖,是自内而外的——将算法抽出来外包;依赖注射的机制更复杂、涵盖面更广,是自外而内的——从外部嵌入定制功能。后者被广泛地用于框架应用之中,尤以Spring Framework和Google Guice为代表。”

引号听得起劲:“这下热闹了,设计模式、框架与OOP范式全搅和到一块了。”

“还有GP范式呢。”冒号顺接话题,“让我们再用C++的模板来实现一下Authenticator类吧。没有继续采用Java,是因为它的泛型仍离不开子类型多态。”

说着,他换上了C++代码——

#include <string>
#include <map>

using namespace std;

template <typename KeyValueKeeper, typename Encrypter>
class Authenticator
{
private:
    KeyValueKeeper keeper;
    Encrypter encrypter;
public:
    void save(const string& user, const string& password)
    {
        keeper.store(user, encrypter.encrypt(password));
    }

    bool authenticate(const string& user, const string& password) const
    {
        string storedPassword;
        if (!keeper.retrieve(user, storedPassword)) return false;

        return storedPassword == encrypter.encrypt(password);
     }
};

class MemoryKeeper
{
private:
    map<string, string> keyValue;
public:
    void store(const string& key, const string& value)
    {
        keyValue[key] = value;  
    }

    bool retrieve(const string& key, string& value) const
    {
        map<string, string>::const_iterator itr = keyValue.find(key);
        if (itr == keyValue.end()) return false;

        value = itr->second;  
        return true;
    }
};

class PlainEncrypter
{
public:
    string encrypt(const string& plainText) const { return plainText; }
};

class Sha1Encrypter
{
public:
    string encrypt(const string& plainText) const { /* 省略代码  */ }
};

namespace
{
    template <typename K, typename E>
    void test(Authenticator<K, E> authenticator) // 参数多态
    { /* 省略代码  */ }
}

int main()
{ 
    test(Authenticator<MemoryKeeper, PlainEncrypter>());
    test(Authenticator<MemoryKeeper, Sha1Encrypter>());
    return 0;
}

“以上代码与Java版的策略模式代码很相似,主要的区别是把KeyValueKeeper和Encrypter两个接口换成了模板参数。由于模板是在编译期间实例化的,因此没有动态绑定的运行开销,但缺点是不能动态改变策略[5]。”冒号分析道,“至此,我们通过一个验证类的三种解法,分别展示了三种形式的多态:基于类继承的多态、基于接口继承的多态和基于模板的多态。它们殊途同归,都能让代码更简洁、更灵活、可重用性更高、更易维护和扩展。”

问号想到一个问题:“C语言既没有子类型多态也没有参数多态,又如何保证高质量的C程序呢?”

冒号眉梢轻挑:“C语言有指针啊,C++、Java和C#的多态在底层就是用指针实现的。C中的函数指针比Java中的接口更加灵活高效,当然对程序员的要求也更高。”

引号蓦地记起:“重载不也是一种多态吗?”

“刚才所说的多态都属于通用多态(universal polymorphism)。此外,还有一类特别多态(ad-hoc polymorphism),常见有两种形式。一种是强制多态(coercion polymorphism),即一种类型的变量在作为参数传递时隐式转换成另一种类型,比如一个整型变量可以匹配浮点型变量的函数参数。另一种就是重载多态(overloading polymorphism),它允许不同的函数或方法拥有相同的名字。特别多态浅显易懂,其重要性与通用多态也不可同日而语,故不在我们关注之列。只是要注意一点,正如子类型应遵守超类型的规范,同名的函数或方法也应遵守相同的规范。如果为贪图取名方便而滥用重载,早晚因小失大。”冒号告诫道。

逗号突发奇论:“一个多态类型的对象可以在不同的类型之间变来变去,是不是叫‘变态类型’更生动些?”

“我看你就属于典型的变态类型。”句号乘机拿他开涮。

全班哈哈大笑。

,插语

  1. 参见§5.2。

  2. 虽然C#具体的泛型类型是在运行期间实例化的,但每类泛型对应相同的实现代码,故变量的行为仍是在编译期间决定的。

  3. salt、nonce和IV都是密码学中的术语,是在加密过程中混入的一次性数据,以增加预计算攻击(如字典攻击)的难度。

  4. 这被称为constructor injection,另外两种常用的注射方法是setter injection和interface injection。

  5. 对用Java实现的Authenticator类(策略模式版)稍作修改,就能让客户动态改变策略。

。总结

  • 在静态类型语言中,继承是多态的基础,多态是继承的目的。

  • 多态结合了静态类型的安全性和动态类型的灵活性。

  • 多态可分为通用多态和特别多态两种。

  • 通用多态主要包括参数多态和包含多态(或子类型多态)。它们都是为了克服静态类型过于严格的语法限制。

  • 特别多态主要包括强制多态和重载多态。

  • 参数多态是静态绑定,重在算法的普适性,好让相同的实现代码应用于不同的场合。

  • 包含多态是动态绑定,重在接口与实现的分离度,好让不同的实现代码应用于相同的场合。

  • 策略模式授予客户自由选择算法(策略)的权力。

  • 模板方法模式重在稳定坚固的骨架,策略模式重在灵活多变的手腕。

  • 合理地运用基于类继承的多态、基于接口继承的多态和基于模板的多态,能增强程序的简洁性、灵活性、可维护性、可重用性和可扩展性。

“”参考

  1. Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides.Design Patterns: Elements of Reusable Object-Oriented Software.Boston, MA:Addison-Wesley,1994.315-323

  2. Luca Cardelli,Peter Wegner.On understanding types, data abstraction, and polymorphism.Computing Surveys,1985,17(4):471-522

Be Sociable, Share!

相关文章

分享/保存
 请您评分1星(很差)2星(不行)3星(一般)4星(不错)5星(很棒) (已有1人评分,平均分为:5.00 / 5)

8 comments to 冒号课堂§10.1:多态类型

  • Todd

    文中提到“一个是在语义上遵循里氏代换原则,另一个是在语法上支持多态(polymorphism)机制”,结合我最近了解的Design by Contract,想到:在C++,Java,C#等语言中,继承/接口只有显式的语法约束,没有显式的语义约束。比如文中第一版Authenticator的store和retrieve,只能由实现者保证其语义正确性。

    支持Design by Contract的语言能从一定程度上帮助保证语义正确性,其方法是在基类中定义class invariant,派生类在继承语法的同时也继承了语义(可能不完整)。和传统OO语言相比,它的进步在于提供了更显式地保证语义正确性的手段。

    除了Design by Contract外,Unit Test也是保证语义正确性的手段。适用于Authenticator的Unit Test Case都应该适用于其派生类。

    • DbC除了能保证基类的class invariant在派生类中仍然有效外,还能保证基类的precondition在派生类中不被强化,postcondition不被弱化。

      • Todd

        关于这点有个例子想不明白,请指教。比如:IAccount表示抽象的银行账户,定义了Transfer(IAccount target, double amount)转账方法;PersonalAccount和EnterpriseAccount是它的两个派生类;PersonalAccount要求每次转账金额amount < 10000,EnterpriseAccount则无限制。那么是不是说PersonalAccount强化了precondition,LSP原则在这里被打破了?

        • 严格说来这的确违背了LSP。假设IAccount对Transfer的amount没有限制,那么当一个类型为IAccount的对象调用Transfer时,代码中到底该不该判断该对象究竟属于PersonalAccount还是EnterpriseAccount?如果作这样的判断,那么(多态)抽象被破坏,代码可能违背开闭原则(假如今后出现新的IAccount子类型,对amount有新的限制怎么办?)。如果不作判断,又可能抛出异常。唯一的补救办法是:在IAccount的Transfer规范中声明,不同的子类型可能会因amount不符合条件而抛出异常。
          在Java中这样的例子也不少,比如接口List含有add、remove等方法,但有些List的子类型并不支持add或remove(如readonly List)。因此,List的规范中明确指出,有些子类型可能抛出UnsupportedOperationException的异常。这样客户会注意在代码中捕捉该类异常,以防万一。这已是最大限度地维护LSP了,虽然并不是那么理想。

  • Pat

    好文章,很受益!对于文中一处所述不太理解:以上代码与Java版的策略模式代码很相似,主要的区别是把KeyValueKeeper和Encrypter两个接口换成了模板参数。由于模板是在编译期间实例化的,因此没有动态绑定的运行开销,但缺点是不能动态改变策略[5]。
    5. 对用Java实现的Authenticator类(策略模式版)稍作修改,就能让客户动态改变策略。

    我的疑惑是,
    1)这文章中的C++代码跟JAVA代码的可比性不太清楚,可能是我的C++太差。您提到的GP范式,JAVA不支持么?
    2)但我也想明白您说的“对用Java实现的Authenticator类(策略模式版)稍作修改,就能让客户动态改变策略”该如何实现? 谢谢

    • 1)文中有一句话:“没有继续采用Java,是因为它的泛型仍离不开子类型多态”。Java虽然支持GP,但在template中的参数不能是简单的Object(C++中template的参数却可以不标明类型),必须涉及到子类型多态(如<T extends KeyValueKeeper>),否则无法在实现代码中调用泛型参数的方法。这样一来,两类多态(子类型多态与参数多态)混在一起,不利于说明问题(本篇的目的是分别介绍三类多态)。
      2)如果Authenticator类为其KeyValueKeeper或Encrypter成员开放setter方法,即可让客户在运行期修改策略。

  • Todd

    文中Authenticator和Keeper/Encrypter的组合关系的建立有两种典型的方式:1. 在Authenticator内部自行创建Keeper/Encypter;2.在Authenticator外部创建Keeper/Encypter,并通过构造函数或Setter传入。起初我没有想明白二者的区别,以至于在使用的时候有困惑。现在我的看法是:如果Authenticator应该负责Keeper/Encypter的生命周期,那就应该放内部;否则就应该从外部传入。比如:人和心脏属于前者,而汽车和轮胎则属于后者。人与心脏的生命周期是绑定的;而汽车报废了或许轮胎还可以继续换到其他地方用。不知这样理解是否正确?

    • 你说得没错,一个是主动的依赖自造,一个是被动的依赖注入。前者拥有更多的自主权,但也承担更多的职责——负责依赖的建造和销毁。Spring、Guice等具有依赖注射机制的框架提倡用后者,由框架来统一管理依赖,不仅减少了应用程序的代码量,更使得依赖体系更加灵活和可定制(通常依赖的是抽象类型而非具体类型,并且可以在metadata中定义),大大减少了系统组件之间的耦合。

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

  

  

  

This blog is kept spam free by WP-SpamFree.