『重构--改善既有代码的设计』读书笔记----Change Value to Reference

有时候你会认为某个对象应该是去全局唯一的,这就是引用(Reference)的概念。它代表当你在某个地点对他进行修改之后,那么所有共享他的对象都应该在再次访问他的时候得到相应的修改。而不会像值对象(Value)一样,不可修改。举个例子,你认识小明,我也认识小明,小明忽然把头发都踢了,这个时候你认识的小明和我认识的小明都是同一个人,都是光头,这个小明就是世界的唯一实例,然而,你有100块钱,我有50块钱,我把50块钱花到只剩20,你手里的100块钱并不会因为我的50块钱改变而改变,不会相应的修改,这就是值对象。放到C++代码中,对于引用的概念,我们可以参照上一篇的例子

void setCustomer(const QString &value)
{
    delete m_customer;
    m_customer = NULL;

    m_customer = new Customer(value);
}

为了让我的m_customer产生“值对象”的概念,我在每次进行set的时候都进行重新创建,这样可以保证我的对象是不可以进行修改的,也就确保了值对象的特性。总结来说,引用对象就像"客户",“账户”这样的东西,每个对象代表真实世界中的一个实物。你可以直接用“==”来比较两个对象是否相等,而值对象更像“日期”,“钱”这样的东西,你不需要去担心它副本的存在,也许系统中存在成百上千个100元钱这种对象,但这并不关系到你,当然如果你要做比较,你也可以重载“==”来做比较。

这也让我们在引用对象和值对象之间产生了两难。有时候,你会从一个简单的值对象开始,在其中保存少量不可修改的数据,而后,你可能希望给这个对象加上一些可修改的数据,并确保对任何一个对象的修改都能影响到所有引用到此对象的地方。这时候你就应该考虑将这个对象变成一个引用对象。

做法:

  • 使用Replace Constructor with Factory Method.
  • 编译,测试。
  • 决定由什么对象负责提供访问新对象的途径。可能是一个静态字典或者一个注册表对象,你也可以使用多个对象作为新对象的访问点。
  • 决定这些引用对象应该事先创建好,还是应该动态创建。如果这些引用对象是预先创建好的,而你必须从内存中将他们读取出来,那么就得确保他们在需要的时候被及时加载。
  • 修改工厂函数,令它返回引用对象。如果对象是预先创建好的,你就需要考虑万一有人索求一个其实并不存在的对象,要如何处理错误。你可能希望对工厂函数使用Rename Method,使其传达这样的信息,它返回的是一个既存对象。
  • 编译,测试。

例子:

class Customer
{
    public:
        Customer(const QString &name) :
            m_name(name)
        {
        }

        QString name() const
        {
            return m_name;
        }
    private:
        const QString m_name;
};

class Order
{
    public:
        Order(const QString &customerName) :
            m_customer(new Customer(customerName))
        {
        }

        void setCustomer(const QString &customerName)
        {
            delete m_customer;
            m_customer = NULL;

            m_customer = new Customer(customerName);
        }

        QString customerName() const
        {
            return m_customer->name();
        }
    private:
        Customer *m_customer;
};

我们引用了Replace Data Value with Object的例子,上一节我们也说过重构并没有因此结束,接下来就是要进行这一步骤。我们有Customer类,可以看到他正在被Order类所使用,并且对待的原则是『值对象』,我们可以看到客户端代码如下

static int numberOfOrderFor(const QList<Order> &orders, const QString &customerName)
{
    int result = 0;

    foreach (Order order, orders)
    {
        if (order.customerName() == customerName)
        {
            result++;
        }
    }

    return result;
}

到目前位置,Customer还是属于值对象,这就造成了就算多个订单属于一个客户,但这个一个客户对于每个Order来说都是自己的。我们希望改变这种现状,希望让客户Customer变成世界唯一。也就是说要让这些订单共享同一个Customer对象,也就意味着每一个客户只该对应一个Customer.

首先我们使用Replace Constructor with Factory Method,以此来控制Customer对象的创建过程,这在以后也是非常重要的,接下来我们在Customer类中定义这个工厂函数。

class Customer
{
    public:
        static Customer *create(const QString &name)
        {
            return new Customer(name);
        }
};

然后把原本调用构造函数的地方都改为调用工厂函数。

class Order
{
    public:
        Order(const QString &customerName)
        {
            m_customer = Customer::create(customerName);
        }
};

然后我们再把原来Customer的构造函数声明为private.

class Customer
{
    private:
        Customer(const QString &customerName)
        {
            m_name = name;
        }
};

现在我们需要决定如何来访问这些唯一的Customer对象,作者比较喜欢通过Order中的一个字段来访问它,但本例没有这样一个明显的字段可以用来访问Customer对象,在这种情况下,通常我们需要创建一个注册表对象来保存所有的Customer对象,以此作为访问点。为了简化我们的例子,我们把这个注册表保存在Customer类的static中,让Customer来作为访问点。

static QHash<QString, Customer *> hashTables;

做完这个决定之后我们需要做下一个重要决定----是应该在请求时才创建还是应该预先创建好。这里我们选择后者,在应用程序的启动代码中,我们先把需要使用的Customer对象加载妥当,这些对象可能来自数据库,也可能来自文件。为求简单起见,我们在代码中明确生成这些对象,反正之后我们总是可以使用Subsititute Algorithm来改变他们的创建方式。

class Customer
{
    public:
    static void loadCustomers()
    {
        new Customer("Leo")->store();
        new Customer("Tom")->store();
        new Customer("Marry")->store();
    }
    private:
    void store()
    {
        hashTables.insert(this.name(), this);
    }
};

现在我们需要修改工厂函数,让它返回预先创建好的Customer对象。

static Customer *create(const QString &name)
{
    return hashTables.value(name);
}

由于create总是返回既有的Customer对象,所以我们应该使用Rename Method来修改这个工厂函数的名称,以便强调这一点。

class Customer
{
    public:
        static Customer *getNamed(const QString &name)
        {
            return hashTables.value(name);
        }
};
时间: 2024-10-23 19:51:15

『重构--改善既有代码的设计』读书笔记----Change Value to Reference的相关文章

『重构--改善既有代码的设计』读书笔记----Extract Method

在编程中,比较忌讳的一件事情就是长函数.因为长函数代表了你这段代码不能很好的复用以及内部可能出现很多别的地方的重复代码,而且这段长函数内部的处理逻辑你也不能很好的看清楚.因此,今天重构第一个手法就是处理长函数--Extract Method,抽取成一个独立的小函数. 我个人来说也很喜欢短小函数,因为他们代表了高强度的复用与灵活性.对于短小函数来说最最关键的就是短小函数的命名,其实你就是给了这些短小函数自我解释的机会,所以你如果给这些短小函数起一个接近其语义的名字,那当你读起长函数来说,就像是阅读

『重构--改善既有代码的设计』读书笔记----Extract Class

在面向对象中,对于类这个概念我们应该有一个清晰的责任认识,就是每个类应该只有一个变化点,每个类的变化应该只受到单一的因素,即每个类应该只有一个明确的责任.当然了,说时容易做时难,很多人可能都会和我一样,一开始建立类的时候信心满满,牢记SRP原则,但随着开发进度的不断进行,很有可能你会给你原本设计好的类增加新字段或者增加新函数,对于少量的增加你可能会因为麻烦,考虑不去单独做一个新类来分解.久而久之,你这个类会变得越来越臃肿,所掌管的责任也会越来越多.这样的类往往还有大量的数据和函数,往往太大而不易

『重构--改善既有代码的设计』读书笔记----Replace Data Value with Object

当你在一个类中使用字段的时候,发现这个字段必须要和其他数据或者行为一起使用才有意义.你就应该考虑把这个数据项改成对象.在开发初期,我们对于新类中的字段往往会采取简单的基本类型形式来保存,但随着我们开发进度的增加,这些简单的数据项就不再那么简单了.比如一开始你会使用一个字符串来表示一串电话号码,但是随后你会发现,这个电话号码已经变的不再纯粹,它可能还需要“格式化”,“抽取取号”等特殊行为.一开始你可能会不以为意,觉得这个数据项就这么一两个,不会对你造成影响.但重复代码(Duplicate Code

『重构--改善既有代码的设计』读书笔记----Replace Method with Method Object

有时候,当你遇到一个大型函数,里面的临时变量和参数多的让你觉得根本无法进行Extract Method.重构中也大力的推荐短小函数的好处,它所带来的解释性,复用性让你收益无穷.但如果你遇到上种情况,你可能会天真的以为我只要适当的进行Replace Temp with Query,就可以把这种现象给化解.但情况往往事与愿违,不能达到你所理想的高度.这个时候你需要用到重构中的杀手锏--Replace Method with Method Object,这个手法出自Kent Beck [Beck].

『重构--改善既有代码的设计』读书笔记----Move Method

明确函数所在类的位置是很重要的.这样可以避免你的类与别的类有太多耦合.也会让你的类的内聚性变得更加牢固,让你的整个系统变得更加整洁.简单来说,如果在你的程序中,某个类的函数在使用的过程中,更多的是在和别的类进行交互,调用后者或者被后者调用,那么你就要注意了,你要去判断这个类是否真正适合他原来所在的类. 简单来说,这套手法就是在该函数最常引用的新类中建立一个有着类似行为的新函数,让旧函数变成一个单纯的委托函数或者完全删掉. Move Method是重构理论的支柱.如果一个类的责任太多,或者一个类和

『重构--改善既有代码的设计』读书笔记----代码坏味道【3】

星期六了,适当出去放松了下,回来继续我们重构的话题.今天是坏味道[3]了,很多朋友跟我私信,叫我把坏味道出完,再出手法.其实这是有道理的,很多时候,"发现"远比"怎么做"重要的多.就拿设计模式来讲,GoF里面的设计模式相信有很多人都了解过.具体的设计模式应该怎么实现啊相信有很多人都背的滚瓜烂熟,但问题的难点往往在于你应该什么时候用这个设计模式.重构也一样,手法步骤都是死的,关键在于应该发现什么时候应该重构.所以,我还是决定继续出坏味道,把坏味道全部出完我们再去学手法

『重构--改善既有代码的设计』读书笔记----Substitute Algorithm

重构可以把复杂的东西分解成一个个简单的小块.但有时候,你必须壮士断腕删掉整个算法,用简单的算法来取代,如果你发现做一件事情可以有更清晰的方式,那你完全有理由用更清晰的方式来解决问题.如果你开始使用程序库,发现其中库提供的功能特性和你的代码重复,那么你也应该改变你原来的算法.或者当你想要修改原先的算法,让他去做一件和原先略有差异的事情,这时候你也可以把原先的算法替换成一个较易修改的算法,让后续修改来的简单点. 使用这个手法之前,确保自己已经充分了解原先函数,替换巨大而复杂的算法是很复杂的,你可以先

『重构--改善既有代码的设计』读书笔记---Duplicate Observed Data

当MVC出现的时候,极大的推动了Model与View分离的潮流.然而对于一些已存在的老系统或者没有维护好的系统,你都会看到当前存在大把的巨大类----将Model,View,Controller都写在了一个widget中.一个分层良好的系统,应该将处理用户界面和处理业务逻辑的代码分开.原因如下 如果你此时需要用不同的用户界面来展示数据,比如微软Excel中的饼状图和折线图,他其实内部展示的数据是一样的,但如果你把这两层用户界面逻辑都放在一个widget中去的话,你就会让这个wiget变得复杂无比

『重构--改善既有代码的设计』读书笔记----Replace Array with Object

如果你有一个数组,其中的元素各自代表不同东西,比如你有一个 QList<QString> strList; 其中strList[0]代表选手姓名,strList[1]代表选手家庭住址,很显然这个数组表示的含义已经太多,你需要用对象来替换数组,并且对于数组中的每个元素,以一个字段来表示. 数组是一种常见的用以组织数据的数据结构,不过,它们应该只用于“以某种顺序容纳一组相似对象”.对于上面的例子你可以看到一个数组容纳了不同对象,这会给使用数组的客户带来麻烦,因为他们很难记住数组的第一个元素是姓名,