设为首页 加入收藏

TOP

关于接口的设计与声明--对封装性的理解[C++](一)
2016-04-29 12:55:03 】 浏览:1098
Tags:关于 接口 设计 声明 封装 理解

设计与声明

所谓软件设计,是“令软件做出你希望它做的事情”的步骤和方法,通常以颇为一般性的构想开始,最终十足的细节,以允许特殊接口(interface)的开发。这些接口而后必须转换为C++声明式。本文讨论对良好C++接口的设计和声明。

1. 让接口容易被正确使用,不易被误用

C++拥有许多的接口,function接口,class接口,template接口….每一种接口实施客户与你的代码互动的手段。理想情况下,客户总是会准确的使用你的接口并获得理想的结果,而如果客户错误的使用了接口,代码就不应该通过编译。

用结构体限制参数类型

假设我们现在需要做一个表示时间的class

class Date {
public:
    Date(int month, int day, int year);
    ...
};

乍看起来,这个类的构造函数并没有什么问题,但其实存在着很多的隐患。我们当然希望用户可以准确的使用我们的类,但用户却有可能因为某些特定的原因无法正确使用我们的类,例如没有按照月,天,年的顺序来完成构造。而此时,为了避免用户犯错,我们需要强制用户按照我们的设计来用这个类:

//  special design
//  缺省情况下,struct内部都是public访问限制。
struct Day {
explicit Day(int d) : val(d) { }
int val;
};
struct Month {
explicit Month(int m) : val(d) { }
int val;
};
struct Year {
explicit Year(int y) : val(d) { }
int val;
};

class Date {
public:
    Date(const Month &m, const Day &d, const Year &y);
    ...
};

Date d1(30, 3, 1996); // error!
Date d2(Month(3), Day(30), Year(1996)); // right!

用struct来封装数据,可以明智而审慎地导入新类型并预防“接口被误用”。

一旦类型限定了,限定其值也是合情合理的了。例如一年只有12个月,所以Month应该反映这一点。办法之一就是用enum表现月份,但enum不具备我们希望的类型安全性,例如enum可以被当做一个int使用。比较安全的做法是:预先定义所有有效的Month。

class Month {
public:
    static Month Jan() { return Month(1); }
    static Month Feb() { return Month(2); }
    ....
    static Month Dec() { return Month(12); }
private:
    explicit Month(int m);
    ..
};

Date d(Month::Mar(), Day(30), Year(1996));

以函数替换对象,表现某个特定的月份是一种相当不错的方法。

限制类型内什么能做,什么不能做

除非有更好的理由,否则尽量让你的type的行为与内置type一致!

用户很清楚像int这样的type有什么行为,所以你应该努力让你的type在合情合理的前提下也有相同的操作。例如,如果a和b都是int,那么对a*b赋值就是不合法的。

避免无端与内置类型不兼容,真正的理由是为了==提供行为一致的接口==。很少有其他性质比”一致性“更能导致”接口被正确使用“,也很少有性质比得上”不一致性“更加剧接口的恶化。

2. 设计class犹如设计type

C++就像其他OOP语言一样,当你定义一个新class,也就定义了一个新的type。包括,重载函数和操作符、控制内存的分配和归还、定义对象的初始化和析构……全都在你控制,因而你应该带着和“语言设计者当初设计语言内置类型时”一样的谨慎来设计class。以下给出了部分class设计规范。

新type的对象应该如何被创建和销毁?这回应该到你如何设计class的构造函数和析构函数以及内存分配函数和释放函数。 对象的初始化和对象的赋值有什么样的差别?这决定了你如何设计构造函数和赋值操作符。最重要的是别混淆“初始化”和“赋值”,因为他们对应不同的函数调用。 新type对象如果被passed by value,意味着什么?记住,copy构造函数用来定义一个type的pass by value如何实现。 什么是新type的“合法值”?这意味你的成员函数必须进行错误检查工作,也影响了函数抛出的异常、以及函数异常明细列。 你的type需要配合某个继承体系吗?如果你继承自某些既有的class,你就会受到这些class设计的束缚,特别是受到他们的函数是virtual或non-virtual的影响。如果你允许你的class被其他class继承,那会影戏到你的析构函数是否会virtual。 你的新type需要什么样的转换?因为你的type存在于其他大量的type之间,这决定了你是否需要让自己type有途径转换为其他的type(隐式还是显式的?) 什么样的操作符和函数对此新type而言是合理的?这取决于你的成员函数的设计。 什么样的标准函数应该驳回?那些就是你声明为private的对象。 谁该取用新type的成员?这决定了如何安排函数是public,protected还是private,以及那些函数/类是friend。 什么是新type的“未声明接口”?他对效率、异常安全性以及资源运用提供何种保证? 你的新type有多么一般化?如果你并不是为了定义一个新type而是要定义一整个type家族,那么应该定义一个新的class template。 你是否真的需要一个新的type?如果你只是为了给base class添加某些功能,那么定义一个或多个non-member 函数或template,更好。

设计class是一件非常具有挑战的事情,所以如果你希望设计一个class,最好像设计一个type一样,把各种问题都思考一遍。

3. 宁以pass by reference to const替换pass by value

在缺省情况下C++总是以pass-by-value的方式传递对象至函数,实际上,就是传递复件,而这些复件都是由copy构造函数产生的,这可能使得pass-by-value称为昂贵而耗时的操作。

问题产生

class Person() {
public:
    Person();
    virtual ~Person();
    ...
private:
    std::string name;
    std::string address;
};
class Student : public Person {
public:
    Student();
    ~Student();
    ...
private:
    std::string schoolName;
    std::string schoolAddress;
};

// in main:
bool checkStudent(Student s);
Student one;
bool whoh = checkStudent(one);

在checkStudent

首页 上一页 1 2 3 4 5 下一页 尾页 1/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇nyoj 712 探 寻 宝 藏(双线dp 第.. 下一篇nyoj 711最舒适的路线(第六届河南..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目