Extract Class (提炼类)

Summary: 

某个类做了应该由两个类做的事。建立一个新类,将相关字段和函数从旧类搬移到新类。

Motivation:

我们都知道,一个类应该是一个清楚的抽象,处理一些明确的责任,但是在实际工作中,类会不断成长扩展,随着责任不断增加,这个类会变得过分复杂。如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此相依,这就表示应该将它们分离出去。一个有用的测试就是问自己,如果搬移了某些字段和函数,会发生什么事?其他字段和函数是否因此变得无意义?

另一个往往在开发后期出现的信号是类的子类化方式。如果你发现子类化只影响类的部分特性,或如果你发现某些特性需要以一种方式来子类化,某些特性则需要以另一种方式子类化,这就意味着你需要分解原来的类。

Mechanics:

1.决定如何分解类所负的责任。

2.建立一个新类,用以表现从旧类中分离出来的责任。

如果旧类剩下的责任与旧类名不符,为旧类更名

3.建立“从旧类访问新类”的链接关系

有可能需要一个双向链接。但是在真正需要它之前,不要建立“从新类通往旧类”的链接

4.对于想搬移的没一个字段,运用Move Field 搬移之。

5.每次搬移后,编译、测试。

6. 使用Move Method将必要函数搬移到新类。先搬移较低层函数(“被其他函数调用”多于“调用其他函数”者),再搬移较高层函数。

7.每次搬移之后,编译、测试

8. 检查,精简每个类的接口

如果建立起双向连接,检查是否可以将它改为单向连接。

9决定是否公开新类。如果的确需要公开它,就要决定让它成为引用对象还是不可变的值对象。

范例

我们从一个简单的person类开始

public class Person
{
    private String name;

    private String officeAreaCode;

    private String officeNumber;

    public String getName()
    {
        return name;
    }

    public String getTelephoneNumber()
    {
        return ( "(" + officeAreaCode + ")" + officeNumber );
    }  public String getOfficeAreaCode()
    {
        return officeAreaCode;
    }

    public void setOfficeAreaCode( String arg )
    {
        officeAreaCode = arg;
    }

    public String getOfficeNumber()
    {
        return officeNumber;
    }

    public void setOfficeNumber( String arg )
    {
        officeNumber = arg;
    }

}
          在这个例子中,我们可以将与电话号码相关的行为分离到一个独立类中。首先要定义一个TelephoneNumber类来表示“电话号码”这个概念
public class TelephoneNumber
{

}
          然后,建立从Person到TelephoneNumber的链接:
public class Person
{
    private TelephoneNumber officeTelephone = new TelephoneNumber();
...
}

现在,我们运用Move Field移动一个字段:

public class TelephoneNumber
{
    private String areaCode;

    String getAreaCode()
    {
        return areaCode;
    }

    void setAreaCode( String arg )
    {
        areaCode = arg;
    }
}
public class Person
{ 
    public String getTelephoneNumber()
    {
        return ( "(" + getOfficeAreaCode() + ")" + officeNumber );
    }

    public String getOfficeAreaCode()
    {
        return officeTelephone.getAreaCode();
    }

    public void setOfficeAreaCode( String arg )
    {
        officeTelephone.setAreaCode( arg );
    }

     然后我们可以移动其他字段,并运用Move Method将相关函数移动到TelephoneNumber类中:

public class TelephoneNumber
{
    private String areaCode;

    private String number;

    public String getTelephnoeNumber()
    {
        return ( "(" + getAreaCode() + ")" + number );
    }

    String getAreaCode()
    {
        return areaCode;
    }

    void setAreaCode( String arg )
    {
        areaCode = arg;
    }

    String getNumber()
    {
        return number;
    }

    void setNumber( String arg )
    {
        number = arg;
    }
}
public class Person
{
    private TelephoneNumber officeTelephone = new TelephoneNumber();

    private String name;

    public String getName()
    {
        return name;
    }

    public String getTelephoneNumber()
    {
        return officeTelephone.getTelephnoeNumber();
    }

    TelephoneNumber getOfficeTelephone()
    {
        return officeTelephone;
    }

}
    下一步要做的决定是:要不要对用户公开这个新类? 我们可以将Person中与电话号码相关的函数委托至TelephoneNumber,从而完全隐藏这个新类;也可以直接将它对用户公开。我们还可以将它公开给部分用户。

如果选择公开新类,就需要考虑别名带来的危险。如果公开了TelephoneNumber,而有个用户修改了对象中的areaCode字段值,我们又怎么能知道呢?而且,做出修改的可能不是直接用户,而是用户的用户。

面对这个问题,我们又下列几种选择。

1.允许任何对象修改TelephoneNumber对象的任何部分。这就使得TelephoneNumber对象成为引用对象,于是我们应该考虑使用Change Value to Reference。这种情况下,Person应该是TelephoneNumber的访问点。

2.不允许任何人不通过Person对象就修改TelephoneNumber对象。为此,我们可以将TelephoneNumber设为不可修改的,或为它提供一个不可修改的接口。

3. 另一个办法是:先复制一个TelephoneNumber对象,然后将复制得到的新对象传递给用户。但这可能会造成一定程度的迷惑,因为人们会认为他们可以修改TelephoneNumber对象值。此外,如果同一个TelephoneNumber对象被传递给多个用户,也可能在用户之间造成别名问题。

Extract Class是改善并发程序的一种常用技术,因为它使你可以为提炼后的两个类分别加锁。如果你不需要同时锁定两个对象,就不必这样做。

这里也存在危险性。如果需要确保两个对象被同时锁定,就面临事务问题,需要使用其他类型的共享锁。这是一个复杂的领域,比起一般情况需要更繁重的机制。事务很有实用性,但是编写事务管理程序则超出了大多数程序员的职责范围。

你可能感兴趣的:(Extract Class (提炼类))