comp.object新闻组上发布的一个关于OO思考和重构的极好例子,非常简单,但请仔细揣摩,可以把自己的结果拿出来大家品评

merlinran 2003-12-18 10:24:38
First, write a program that can forward sort lines of text from standard
input and write them to standard output. Don't worry about any other
requirements and don't write the program to do anything more than the
sort in question. Oh, and since this is a learning exorcise, don't use
the STL... :-)

Now add in a command line option to "sort in reverse". Notice that
somewhere in your program, you have to do something different based on
whether the sort is forward or reverse. Now I want you to be creative
and arrange the code so that the if check on the reverse flag is only
done once during runtime. In other words, if you put the check inside a
loop somewhere, it will be called every pass, instead put the check
outside all the loops and use an abstract class that has a method that
tells the algorithm which which item goes first, then the if check
creates a concrete object from a sub-class of that abstract class.

Now add a command line flag to sort numerically. If this flag is set,
the program should parse input as integers and sort them. It should not
sort strings with this flag set. :-) Again, the flag should only be
examined once during runtime. Of course you must make sure that the
"sort in reverse" flag still works.

When you are done with this step, you should have a program that (1)
checks the flags and creates some objects based on how they are set, (2)
reads the input and uses one or more of the objects created above to
parse it, (3) sorts the items, again using one or more of the objects
created in step 1 to decide the sort order, then (4) outputs the items,
again using one or more of the objects created in step 1.

Now ask yourself, how hard would it be to use the sorting algorithm for
sorting doubles? What if you wanted it to sort shapes by size? Would you
have to make any changes to the actual input, sort, and output sections
of the program, or only to the "check the flags and create some objects"
part?

Extra Credit: add a command line option that allows the user to change
the sort algorithm being used...

If desired, e-mail the program to me (replace "postmaster" with
"daniel_t"), or post it on this newsgroup and let us critique it...

As you come to understand what "they" are talking about in the example,
you will find that you routinely write program units that can work in a
variety of contexts, just like the input, sort, and output units in the
above program work for many different situations, even ones you haven't
thought of yet.
...全文
93 49 打赏 收藏 转发到动态 举报
写回复
用AI写文章
49 条回复
切换为时间正序
请发表友善的回复…
发表回复
surfl 2004-01-15
  • 打赏
  • 举报
回复
11
merlinran 2004-01-14
  • 打赏
  • 举报
回复
deavilness(侠盗裸奔汉):
typedef是编译时处理的,不能在运行时动态更改,所以你的第一段代码行不通。当然,这和讨论的话题没什么关系,只是提醒你犯的一个小错误。
deavilness 2004-01-11
  • 打赏
  • 举报
回复
再补充:

具体的排序算法和顺序策略,已经从 Sort 函数中脱离出来,只要改变 or 和 ar,那么就会出现很多不同的行为组合。至于是倒序还是顺序,是冒泡法还是插入法,可以自由发挥组合。

我写的是设计上的思路,并没有涉及排序的具体算法的实现,也没有涉及顺序策略的具体实现,如果跑题了请大家原谅。
deavilness 2004-01-11
  • 打赏
  • 举报
回复
补充,上面的模板方法是模仿 STL 的做法。

当然,这一个策略也可以用 OO 实现,其实说白就就是一个 Strategy 模式:

class Ar {
virtual void sort() = 0;
};

class Or {
virtual void isFrontOf(Iterator itor1, Iterator itor2) = 0;
};

.....

/* 这里制定默认的排序算法和顺序策略 */
/* oneAr 和 onrOr 是实现了接口 Ar 和 Or */
Ar* defaultAr = new oneAr();
Or* defaultOr = new oneOr();

void Sort(Iteraot itor, Ar ar = defaultAr, Or or = defaultOr) {
ar.sort(itor, or);
}

int main(void) {
Or* or;
Ar* ar;
switch (type) {
case 'R':
ar = new oneAr(); break;
default:
....
}

switch (ar) {
case pop:
or = new oneOr(); break;
.....
}

Sort(itor, ar, or);
}
deavilness 2004-01-11
  • 打赏
  • 举报
回复
我个人认为,设计上要保持灵活性,应该保持两个策略接口:

typedef oneOrder defaultOrder;
typedef oneArthmetic defaultArthmetic;

template<
class Iterator,
class OrderArithmetic = defaultOrderArthmetic,
class Order = defaultOrder
>
void Sort(Iterator itor) {
OrderArithmetic<Iterator> oa;
Order<Iterator> order;
oa.sort(itor, order);
}

这里 OrderArithmetic 必须实现 sort(Iterator, Order) 接口,实现排序算法
Order 则必须实现 isFrontOf(Iterator itor1, Iterator itor2) 算法,以判断 itor1 和 itor2 哪个顺序在前面。

然后就可以替换不同的 OrderArithmetic 和 Order 策略来实现排序。

int main(void) {
....
switch (type) {
case 'R':
typedef ReservOrder order; break;
default:
typedef NormalOrder order; break;
}

switch (ar) {
case pop:
typedef PopAr ar; break
.....
}

Sort(itor, order, ar);
}
孩皮妞野 2003-12-31
  • 打赏
  • 举报
回复
假定要求命令行如下:
sort [-t type] [-r] [-i]

其中type默认为s[字符串], 其他要求实现的类型为u[无符号整数],i[整数],f[浮点数]

如果有-r表示逆序;

-i仅对type为s时有效,其他类型忽略此选项。

作为一个练习还是不错的。
ambition2005 2003-12-30
  • 打赏
  • 举报
回复
我知道了我差在哪儿了,谢谢楼主和 noproblem_jyb(noproblem)

只看书是没用的:)实践很重要
aojunpeng313001 2003-12-29
  • 打赏
  • 举报
回复
受教良多!
neptunez 2003-12-29
  • 打赏
  • 举报
回复
不错。其实仔细研究一下boost,stl的设计,就不难理解楼主的用意。
i_jianyong 2003-12-27
  • 打赏
  • 举报
回复

mark一下先
boy8765 2003-12-27
  • 打赏
  • 举报
回复
标记。
DerryZhang 2003-12-26
  • 打赏
  • 举报
回复
好贴
glacierrr 2003-12-26
  • 打赏
  • 举报
回复
抱歉,我没有看e文,只看了 merlinran(天行者) 的中文翻译
cai114 2003-12-26
  • 打赏
  • 举报
回复
好现在没时间考完试再来写写
noproblem_jyb 2003-12-26
  • 打赏
  • 举报
回复
to ambition2005(惑):

>>
不要在程序中有太多的限制, design by constraints, design by policy。如果你认为用了stl程序的限制就会少一点,那你错了。constraints不光是在data type上,而是在程序的任何地方。如,你用了<(less than operator),就不可以用>(great than operator)。
仔细看看stl中search algorithm和其他一些algorithm的接口把,为什么他们要多一个Predicate template parameter呢?
=================================================================================
我真的不能理解

OK! Here is the magic. I write the code that sort ints, string version is waiting for you^_^.


template<class T>
class SortAlgoInterface
{
public:
virtual bool operator()(const T& lhs, const T& rhs) = 0;
};

template<class T>
class SortGreaterThan : public SortAlgoInterface<T>
{
public:
virtual bool operator()(const T& lhs, const T& rhs)
{
return lhs > rhs;
}
};

template<class T>
class SortLessThan : public SortAlgoInterface<T>
{
public:
virtual bool operator()(const T& lhs, const T& rhs)
{
return lhs < rhs;
}
};

template<class T>
class DoSort
{
public:
DoSort(const SortAlgoInterface<T>& sortAlgo)
: sortAlgo_(sortAlgo)
{}

template<class Container>
void sort(Container& c)
{
typedef Container::iterator IT;
IT begin = c.begin();
IT end = c.end();

// sort elements in container c using sortAlgo_ predicate.
}

private:
const SortAlgoInterface<T>& sortAlgo_;
};


int main(int argc, char *argv[])
{
// decompose the command line and get the following flag
typedef std::string Predicate;
Predicate predicate("reverse sort");

SortAlgoInterface<int> *sortAlgo;

if (predicate == "reverse sort")
sortAlgo = new SortLessThan<int>();
else
sortAlgo = new SortGreaterThan<int>();

int val;
std::vector<int> vi;

while (std::cin >> val)
{
vi.push_back(val);
}

DoSort<int> Sort(*sortAlgo);
Sort.sort(vi);

return 0;
}
merlinran 2003-12-26
  • 打赏
  • 举报
回复
抱歉,这两天比较忙,对自己提出的问题关注不够,望谅。
我不是高手,但我乐于提出我自己的看法。在这个版中很难有高手出现。空等着高手现身说法,十有八九会落空。

ambition2005(惑):
我认为你的程序也并非无可取之处。

首先,你用到了C++提供的数据抽象的工具,把相关的东西,用类结合到一起。
其次,你知道利用派生和虚函数来处理不同的比较策略。半年前,我对OO的理解,也许还不到这个程度。

我的翻译有个错误:
“那个if判断则根据reverse标记创建一个从抽象类中派生的具体类,来具体执行排序。”
实际上,原文中并没有说这个具体类是来执行排序的。这句有点误导人,让你直接从Data类进行了派生。

一些值得改进的地方,主要是对各种责任的划分:
1、类Data不应该处理输入输出,因为不是所有的Data都需要输入输出的。同样,它也不需要排序,并不是所有Data都要排序。Data在此并没有什么意义,程序的目的是排序,那就应该把这个意义表现出来。 像noproblem_jyb(noproblem)所例示的代码就比较好。

2、只处理三种情况(正常、反向、数值),就用了四个派生类。如果再加一个参数,会要多少个?这种组合式的继承,应该避免。

3、处理命令行参数是件比较麻烦的事。如果有过经验,则会想到把解析命令行的功能用另一个类来实现。到时,只要向该类查询IsReverse或者IsNumeric就行了。

我的想法,是根据每一步的要求:
1、写一个功能分解的程序,用数据结构作为中介。因为第一步的需求并没有说还要做别的事情。让main分别调用输入、排序和输出三个子函数,以list<string>或者类似的结构作为三个子函数之间的沟通桥梁。这里,没有用到OO。

2、要反向排序了,但处理的还是字符串。那就把比较元素的方法抽象出来,比如叫CompareCriterion吧。根据正向还是反向,分别创建NormalCompareCriterion或者ReverseCompareCriterion。此时排序就应该由一个函数变成类了,因为它需要保存CompareCriterion的具体实例。Sorter类暂时只需要两个函数:SetCompareCriterion(CompareCriterion*)和Sort(list<string>&),当然,这里的名字和参数类型只是举例。

3、按数值排序。这里我想到两种策略,一是输入输出都不变,像ambition2005那样用组合式的继承。缺点嘛,我前面提到过。

另一个是把输入和输出都抽象出来,派生子类,比如一个叫StringInputer,另一个叫NumericInputer。输出和输入是对称的关系,因此可以把这两个功能由同一个类来实现。
如果采用第二种办法,那两个子类得到的结果不一样,一个是字符串列表,另一个是数字的列表,怎么办?我觉得在此由主函数处理也未尝不可。

那么CompareCriterion呢?为Numeric也实现单独的吗?不需要。对不同的数据类型使用同一套算法,这正是模板大显身手之处。采用模板,对浮点数排序的要求,我们也自然地照顾到了。

再下一步:能够选择排序算法。我们已经把排序变成了类,只需要把操作提升到一个虚基类中,具体的排序算法派生之,就行了。

这只是我的想法,多多指教。
ambition2005 2003-12-25
  • 打赏
  • 举报
回复
请仔细体会一下“(1) checks the flags and creates some objects based on how they are set”这句话。
================================================================================
我问了一下朋友:
/*noproblem_jyb(noproblem)说的话,“(1) checks the flags and creates some objects based on how they are set”这句说得很对哈,你的程序跟楼主贴的文章完全是两回事,或者说虽然你是用C++和STL做的,但你的设计其实没有乃至什么OO的东西,只是利用了类的封装性而已*/
有点明白了(还是不全明白)



不要在程序中有太多的限制, design by constraints, design by policy。如果你认为用了stl程序的限制就会少一点,那你错了。constraints不光是在data type上,而是在程序的任何地方。如,你用了<(less than operator),就不可以用>(great than operator)。
仔细看看stl中search algorithm和其他一些algorithm的接口把,为什么他们要多一个Predicate template parameter呢?
=================================================================================
我真的不能理解,请指点,谢谢


我真心向您学习,没别的意思
ambition2005 2003-12-25
  • 打赏
  • 举报
回复
谢谢,noproblem_jyb(noproblem)

惭愧惭愧

哎,继续学习去
noproblem_jyb 2003-12-25
  • 打赏
  • 举报
回复
这么粗糙的程序还拿出来。
请仔细体会一下“(1) checks the flags and creates some objects based on how they are set”这句话。
不要在程序中有太多的限制, design by constraints, design by policy。如果你认为用了stl程序的限制就会少一点,那你错了。constraints不光是在data type上,而是在程序的任何地方。如,你用了<(less than operator),就不可以用>(great than operator)。
仔细看看stl中search algorithm和其他一些algorithm的接口把,为什么他们要多一个Predicate template parameter呢?
samfsson 2003-12-25
  • 打赏
  • 举报
回复
作一下标记先!
加载更多回复(29)

64,644

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧