面向对象的设计原则之一:针对接口编程,而不是针对实现编程 (100分)

  • 主题发起人 主题发起人
  • 开始时间 开始时间
以有些结点支持delete,copy,另一些结点只支持delete为例。
=====================================================
当有了新操作以后, 可在 TAbstractVisitor里的增加虚方法
=====================================================
我觉得问题的关键就在于定哪些方法?
如果在定义了delete和copy两个方法,那该类设计就是采用了,
“透明原则”。虽然不支持copy的结点,在acceptvisitor时确实
可以不调用visitor的copy方法。 但问题是,当你提取单个结点时,
根本不知道,该结点实例是否支持copy方法。虽然该结点类,支持visitor接口。
在“透明原则”下,根本无法表明,究竟支持了visitor中的哪些方法。
在我看来还是无法解决楼主,又要“安全性”,又要“透明性”的问题。
 
判断某个派生类是否支持某项操作处理也很简单.例如剪切操作,接口中定义一个function Cut(flag:dword):boolean;
那么在要判断是否支持剪切操作的时候先传递一个参数进去test一下.例如
if INode.cut(TEST_ONLY) then
INode.cut(xxx)...
 
  对树节点使用何种模式,就要看树的情况了。
  树的节点,跟作用于节点上的操作,是两方。树的节点,更多的是静态数据,往往将作
为参数传入(当然,Data上可以放上接口或对象指针之类)。那么,如何封装和管理这些操
作,是作为重点的考虑问题。我们对操作管理的要求:1)增加操作不用修改代码,也就是
说,每增加一个操作,只须增加类就可以;2)操作的增加,不能让操作类本身变得过于复
杂而难于管理(例如增加一个操作,就增加一个类层次,层次过多就变得难于管理。)
  根据我们的要求,再来考察什么模式能够达到或符合这些要求。
  看看Visitor模式,Visitor 模式的特点是,在节点相似的情况下,将操作跟节点分
离,有益于增加操作的同时不用对节点的结构作改动。但,如果节点的结构不同,在增加
Visitor 的同时,要对节点的结构作改动的话,那么管理维护就开始出现麻烦。
  举个例子,有人比较喜欢将各种分类放在一棵树上,有部门信息,职位信息,人员信
息,客户信息,产品信息等等,一古脑放在一棵树上时,那么Visitor层就会变得庞大和复
杂。
  话又说回来,这样的情况毕竟不多见,相对于比较单一内容节点的树,Visitor 模式还
是一个不错的选择。
  至于,将接口或对象指针放到节点中,我觉得并不是太好。因为,这就要求对两方
(节点方跟操作方)都要进行维护。对于操作简单或节点所表达的内涵单一的情况下,两个
都维护,还是问题不大的,但如果趋向于复杂时,维护问题就会凸显。
  上面是我的看法,请大家指教。
 
今天仔细看了一遍上面的帖子.发现大家都有点偏离楼主要问的问题了.
其实我认为楼主问题的意思是它的节点类是一个纯虚类(也可以看作是接口),然后相同的操作名称(例如删除)有不同的动作(具体如何删除).那么他是用派生类来实现,这样操作者只需要调用纯虚类的删除,得益于OO,所以会自动调用正确派生类的具体实现.
他的问题是.如果一个节点有不支持的操作的话,如何处理? 例如一个节点有3个操作,改名,删除,查看属性. 但是有一个节点不支持删除操作,如何解决?
我的解决方式:
1.还是用上面的纯虚类作为基类,不过方法需要改一下,每个操作支持查询,如删除操作 TbaseNode.delete(flag):boolean;
那么派生类如果不支持删除操作的话,查询的时候直接返回false就可以了. 那么操作删除的代码可以这样写
if BaseNode.delete(CAN_DO_THIS) then
baseNode.delete();
2.使用接口替换上面的纯虚类,定义一个接口例如叫INode,包含所以节点都必须实现的功能.如上面的改名,查看属性.所有节点类都必须实现这个接口.对于像删除这样的不是每个节点都支持的操作单独声明一个接口例如叫IDelete, 只在支持这个操作的节点中实现.然后操作删除代码的就可以这么写了.
var idel:idelete;
if INode.queryInterface(Idelete,ide) then
begin
idel.delete();
idel := nil;
end;
 
用接口好象不对, node的属性应该是在运行期决定的, 用属性比较好
 
不知yyanghhong所言何意?
 
我说说我对这个问题理解的思路,请大家指教。
先把节点的操作分一个类,假设有这几个操作:Copy,Paste,Delete,New;
定义接口:
IA = Interface
procedure Copy(const p1,p2: Integer;
Out p3: Integer);
procedure Paste(const p1,p2,p5,p9: Integer;
Out p3: Integer);
end;
IB = Interface
procedure Delete(const p1,p2,p5,p9: Integer;
Out p3: Integer);
procedure New(const p1,p2: Integer;
Out p3: Integer);
end;
对于节点类型1,假设他又Copy,Paste操作,她的实现类是 TNode1 = (TNODETest,IA);
对于节点类型2,假设他有Copy,Paste,Delete,New;操作,她的实现类是 TNODE22 = (TNODETest,IA,IB);
我们现在要做的只是需要节点的操作和这个操作的具体实现类绑定就可以了,例如,对于节点类型为 “ABC”的
节点,它绑定的操作实现类是 “TABCFGH”,绑定的接口是 “IADDGFDG”和“IFGFIFDG”,那你对这类节点进行操作的时候只是
需要调用这个类(接口)的方法就可以了。
当然,这些节点的实现类可以继承一个公共的父类,其实也可以不用继承这个父类,我觉得要根据子类之间是否有共同
的抽象来定了,但是我认为这个父类是不应该实现 “接口” 函数的,不应该定义接口的虚函数。我不赞同把所有的节点操作函数
定义成父类的虚函数,如果这样感觉就是一碗炸酱面了。
做的更高级一点,可以把这种节点类型、实现类、实现的接口这些数据写道文件中去动态配置,也就是说,一个节点的操作
可以在运行期间指定这个操作的实现类。这样也就做成了一套框架。
楼主说,希望以统一的方法操作所有的子类(节点操作实现类),我的建议是增加一个协调对象,由这个协调对象再去处理节点操作类
的实现了,也就是说,你的节点所有的操作命令都是直接发送给这个协调对象的,这样,在节点操作那里,就可以统一处理了。
 
用接口去实现是典型的过分设计.
如果有四种操作 Copy,Paste,Delete,New, 做成四个接口, 那样需要16个类去实现不同接口的组合. eg.
AB, ABC, ABCD, AC, ACD, AD, BC, BCD, ..
在vcl里, 都用set of来解决options属性的. eg.
TAnchorKind = (akLeft, akTop, akRight, akBottom);
TAnchors = set of TAnchorKind;
很方便简单
 
就楼主问题的理论上来说,赞同xwings, Application。其实象M$产品的很多类中都是用多接口来实现这种功能的可能性的。熟悉MSHTML编程的人会知道,对应于Document类的接口就有IHTMLDocument, IHTMLDocument2, IHTMLDocument3, IHTMLDocument4, IHTMLDocument5之多,它们不是继承的关系,而是补充的关系。
不同的节点根据自己的功能,实现不同的接口。调用者在需要某种操作时,先尝试获得某个接口指针或引用,如果指针或引用为空则表示不支持,则不调用。在C++、Delphi中,接口指针可以使用QueryInterface方法来得到,在Delphi中,还可以用 obj as interface的方式来得到。
以上是就理论情况讨论。在具体实现时(如treeview),有可能这样做的结果是事倍功半(问题本身比较简单,实现也不难),其实可以考虑别的方法,以上有些网友的方法就不错。
 
接口和继承都可以实现多态性,
但接口更灵活,避免了复杂的继承树
接口也可以继承,但是一个对象能支持多中接口
 
TTreeNodeAction=(tnaAdd,tnaDelete,tnaCopy);
这是TreeNode所有可能的操作。就是你说的最大化。
TTreeNodeActionSet=Set of TTreeNodeAction;
每个节点实现
INodeInterFace=interface
function AcceptAcionts:TTreeNodeActions;
procedure ExecuteAction(AAction:TTreeNodeAction);
end;
TAbstructNode=class(TObject)
public
function AcceptAcionts:TTreeNodeActions;abstruct;
function ExecuteAction(AAction:TTreeNodeAction);Boolean;virtual;
begin
Result:=Not (AAction in AcceptActions);
end;

end;
TNotDeleteNode=class(TAbstructNode)
function AcceptAcionts:TTreeNodeActions;
//result=[tnaAdd,tnaCopy]
function ExecuteAction(AAction:TTreeNodeAction):Boolean;override;
begin
Result:=inherited;
if Result then
begin
end;
end;
end;
当你选择节点时,向节点类查询你实现了什么,可以向你下什么命令。如果已告诉你可以向我下什么操作,仍像我下没有实现的操作,那就是你的问题了。当然我可以拒绝执行。
 
借楼主宝地,打打广告,谢谢!
阳春三月,和Borland专家--刘艺相约上海!
大家好:
 “一年之计在于春”,春天是定目标、打基础关键时刻!
无论你的目标是加薪,成为项目经理,还是让自己的技术水平更上一层楼,
都需要不断地学习,而与高手的交流,仿佛是站在巨人的肩上:站得高,看得远,助力你迅速成为Delphi高手!
应中国项目经理网邀请,Borland专家--刘艺老师将于这个三月来到上海
给大家做<<UML与DELPHI模型驱动开发>>的培训,机会难得!请热爱Delphi的朋友请抓紧时间报名!
届时将会有众多Delphi高手光临现场!热烈的现场讨论以及众多Delphi高手的面对面交流讲师本次培训的特色之一!
在温暖的三月,刘艺与众多Delphi高手与大家相约上海!

中国项目经理网相关培训链接:
[公告]阳春三月,和刘艺老师面对面讨论UML和Delphi面向对象开发!
http://www.china-pm.net/dispbbs.asp?boardID=22&amp;ID=5&amp;page=1
[公告]uml与delphi模型驱动开发课程介绍
http://www.china-pm.net/dispbbs.asp?boardID=22&amp;ID=21&amp;page=1
报名表
http://www.china-pm.net/dispbbs.asp?boardID=22&amp;ID=35&amp;page=1
中国
 
后退
顶部