面向过程到面向对象-入门,心得(实例剖析)

来源:互联网 发布:白金数据 东野圭吾 编辑:程序博客网 时间:2024/06/03 20:00

面向过程到面向对象-入门,心得(实例剖析)

问题描述(略显简洁)

一家书店做促销,部分书籍参与打折,购买量到一定程度有相应的折扣;(例:某书10本以内,不打折,10本以上打9折)。
结账时,打印出账单,账单条目如下(按isbn排序):
isbn,数量,商品价格;
最后打印所有书籍总价格。

问题分析

1)基本分析
书籍有至少有两种:一种打折,一种不打折;
对于一个顾客,买了n本书,需要对每一个顾客的书计算价格,最后得到总价格。

2)深入分析
a)
两种书籍有共同点:isbn,价格;
不同点:折扣,数量;

因此设计一个继承体系的类,用于表示不同种类的书籍(对象)。

b)对于每一个顾客,设计一个书籍类的vector,保存每一本书的信息,到最后遍历这个vector,进行操作即可。
但因为需要排序打印,故用multiset取代vector(可能买多本书籍)。
因为multiset中只能存放的是同一类型的对象,而书籍的类型不一致,故只能存放指针。这里采用智能指针。

面向过程解决方案

1)书籍类的设计:
Quote表示不打折书籍;
Bulk_Quote表示打折书籍,继承自Quote

class Quote {public:    Quote() = default;    Quote(const string& book, double sales_price): bookNo(book), price(sales_price) {}    string isbn() const { return bookNo; }    virtual double net_price(size_t n) const { return n * price; }    virtual ~Quote() = default;protected:    double price = 0.0;    //价格private:    string bookNo;    //isbn};class Bulk_Quote : public Quote{public:    Bulk_Quote() = default;    Bulk_Quote(const string& book, double p, size_t qty, double disc):        Quote(book, p), min_qty(qty), discount(disc) {}    double net_price(size_t n) const override {   //override只出现在声明中,const &等之后        if (n >= min_qty) {            return n * (1 - discount) * price;        } else {            return n * price;        }    }private:    size_t min_qty = 0;    //能打折需要达到的最少数量    double discount = 0.0;    //具体的折扣};

简单解析:
如果有不同的折扣活动,可以通过继承,实现不同的折扣方法。

2)对于每一本书籍,打印其基本信息

//item 书籍(具体的对象);//n 书籍的数量double print_total(ostream& os, const Quote& item, size_t n) {    double ret = item.net_price(n);    //多态调用其价格计算的方法    os << "isbn: " << item.isbn() << " sold: " << n << " total due: " << ret << endl;    return ret;}

3)对于multiset,因为关键字必须定义一个<运算符,故采用继承标准库提供的方法,模版特例化一个版本

template <typename T>struct compare : public std::binary_function<T, T, bool> {    bool operator() (const T& x, const T& y) const {        return x->isbn() < y->isbn();    }};

这样定义之后,就可以定义如下的multiset

multiset<shared_ptr<Quote>, compare<shared_ptr<Quote>>> ms;

简单解释:
1 定义了一个名为ms的multiset
2 其中存放的对象类型为shared_ptr

multiset<shared_ptr<Quote>, compare<shared_ptr<Quote>>> ms;//定义几个对象;Quote q1("11-11", 50);Quote q11("11-11", 50);Quote q2("11-12", 40);Bulk_Quote b1("22-11", 50, 10, 0.1);Bulk_Quote b2("22-12", 40, 10, 0.1);//将对象插入ms,因为ms保存的是指针类型,所以必须用对象显示构造指针;ms.insert(make_shared<Quote>(q1));ms.insert(make_shared<Quote>(q11));ms.insert(make_shared<Quote>(q2));ms.insert(make_shared<Quote>(b1));ms.insert(make_shared<Quote>(b2));//输出信息double total = 0.0;for (auto iter = ms.cbegin(); iter != ms.cend(); iter = ms.upper_bound(*iter)) {    total += print_total(cout, **iter, ms.count(*iter));}cout << "total sales: " << total << endl;

对于输出信息模块,简单解释如下:
1 因为multiset中可能有多个相同的对象,ms.upper_bound(*iter)即使找到该对象的最后一个重复版本的下一个位置
2 因为iter是迭代器,需要解引用得到对象;而muliset中保存的是指针,故再次解引用得到Quote对象;

5)总结:
该方法最大的问题就在于需要用户显示的构造指针,并且暴露了太多的multiset实现的细节。正因为如此,才引入了面向对象的方法。

面向对象方法1

面向过程暴露了太多的实现细节,那可以把那些细节都用一个类表示即可。
1)设计一个类如下:

class Basket_ver1 {public:    void add_item(const shared_ptr<Quote>& sale) {          items.insert(sale);    }    double total_receipt(ostream& os) const;private:    multiset<shared_ptr<Quote>, compare<shared_ptr<Quote>>> items;};double Basket_ver1::total_receipt(ostream& os) const {    double sum = 0.0;    for (auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter)) {        sum += print_total(os, **iter, items.count(*iter));    }    os << "total sales: " << sum << endl;    return sum;}

2)主程序如下:

Basket_ver1 bst;Quote q1("11-11", 50);Quote q11("11-11", 50);Quote q2("11-12", 40);Bulk_Quote b1("22-11", 50, 10, 0.1);Bulk_Quote b2("22-12", 40, 10, 0.1);bst.add_item(make_shared<Quote>(q1));bst.add_item(make_shared<Quote>(q2));bst.add_item(make_shared<Quote>(q11));bst.add_item(make_shared<Quote>(b1));bst.add_item(make_shared<Quote>(b2));bst.total_receipt(cout);

3)总结
因为类中add_item函数接受的是指针,很方便类的设计(自动进行派生类到基类指针的转换),但麻烦了用户(用户得显示的创建指针); 所以,引出了方法2

面向对象方法2

1)问题
方法1中:add_item的参数是基类指针。此时不论用户提供的是基类类型还是派生类类型,都可以存入item中(派生类指针可以自动转化为基类指针)。然而,用户更应该直接提供对象,而不应该提供指针。所以需要修改函数参数为对象类型,函数内部通过对象来构造指针。

那么问题来了:
void add_item(const Quote& sale);
函数接受一个对象的引用,如何创建对应类型的指针呢?
new Quote(sale) or new Bulk_Quote(sale) ?
貌似都不行。

我们希望编写如下代码:
sale.clone();可以根据对象的实际类型创建对应的对象;

这是,我们应该在类的继承体系中添加虚函数clone实现多态;

2)重新设计
a)继承体系类

class Quote {public:    Quote() = default;    Quote(const string& book, double sales_price): bookNo(book), price(sales_price) {}    string isbn() const { return bookNo; }    virtual double net_price(size_t n) const { return n * price; }    virtual ~Quote() = default;    //新添加函数:可以接受左值或右值    virtual Quote* clone() const & {        return new Quote(*this);    }    virtual Quote* clone() const && {        return new Quote(std::move(*this));    }protected:    double price = 0.0;private:    string bookNo;};class Bulk_Quote : public Quote{public:    Bulk_Quote() = default;    Bulk_Quote(const string& book, double p, size_t qty, double disc):        Quote(book, p), min_qty(qty), discount(disc) {}    double net_price(size_t n) const override {   //override只出现在声明中,const &等之后        if (n >= min_qty) {            return n * (1 - discount) * price;        } else {            return n * price;        }    }    //新添加函数:可以接受左值或右值    Bulk_Quote* clone() const & {        return new Bulk_Quote(*this);    }    Bulk_Quote* clone() const && {        return new Bulk_Quote(std::move(*this));    }private:    size_t min_qty = 0;    double discount = 0.0;};

简单解析:
1 先不看右值版本(&&),只看左值版本(&);
2 在Quote中定义一个虚函数clone,返回一个本对象的指针;在Bulk_Quote中覆盖该函数;

b)功能类

class Basket_ver2 {public:    void add_item(const Quote& sale) {          items.insert(shared_ptr<Quote>(sale.clone()));    }    void add_item(Quote&& sale) {           items.insert(shared_ptr<Quote>(std::move(sale).clone()));       }    double total_receipt(ostream& os) const;private:    multiset<shared_ptr<Quote>, compare<shared_ptr<Quote>>> items;};

简单解析:
1 只看接受左值参数版本
2 sale调用clone,因为接受的参数是基类引用类型且调用虚函数,所以在此处一定会发生多态调用,这样返回的指针类型即是正确的。
3 因为返回的是原生指针,需要显示构造智能指针,才能存入items

3)主程序如下:

Basket_ver2 bst;Quote q1("11-11", 50);Quote q11("11-11", 50);Quote q2("11-12", 40);Bulk_Quote b1("22-11", 50, 10, 0.1);Bulk_Quote b2("22-12", 40, 10, 0.1);bst.add_item(q1);bst.add_item(q11);bst.add_item(q2);bst.add_item(b1);bst.add_item(b2);bst.total_receipt(cout);

三种方法的结果都是相同的:

isbn: 11-11 sold: 2 total due: 100isbn: 11-12 sold: 1 total due: 40isbn: 22-11 sold: 1 total due: 50isbn: 22-12 sold: 1 total due: 40total sales: 230

4)总结
该主程序相对于方法1中的主程序区别在于一点:用户只需要提供对象即可插入到bst中,而不需要做任何多余的事情。(显示构造指针)

小结面向对象

最后的主程序或许能简单反映出面向对象编程的思想:面向 对象
1 程序中一切东西都是对象:不论是bst,或q1,b1都是对象,通过调用对象的方法,管理对象;
2 封装:主程序只能看到对象,完全不知道其实现细节(add_item并不是细节,而是解决问题的逻辑),用户只需要把精力集中在逻辑的实现(1 创建对象 2 插入对象 3 打印对象)。当能够弄清楚逻辑的实现时,问题也应该已经得到解决。
3 继承和多态:面向对象的核心实现即是继承和多态。仅仅是修改用户的一处操作,就需要修改整个继承体系类的设计,目的就是为了能够方便用户。如果没有继承和多态,根本无法实现。
4 一切的设计只是为了用户方便。因此写代码之前,首先应该想到逻辑怎么实现,然后想主程序应该怎么写,最后才是类的设计。

路漫漫其修远兮,吾将上下而求索

在这个充斥着大数据,云计算,函数式编程,并发,网络,多线程编程的年代,我才刚刚开始面向对象编程。

慢慢来,不着急。。。

(刚入门,写的很基础,还请各路大神赐教)

0 0