首先感谢各位在前一个问题的探讨和解答,特别是不睡觉的鱼和Ivony,让我茅塞顿开。现在改用抽象类来做笔类,毛笔类、水笔类是继承笔类的具体类。
考虑某企业要管理已购买的笔的情况。这么设计数据库:
把毛笔、水笔的公共属性单独做一张笔数据表(假设这样的公共属性有很多),包含“PenID”“Length”(当然,应该有很多字段,这里简单描述了),
毛笔数据表包含“BrushID”“HairType”“PenID”(PenID做为笔表的外键),水笔表类似处理。显然,当增加一支毛笔的时候需要在笔表和毛笔表中各增加一条记录。那么我在笔类里增加一个方法AddPen();毛笔类里增加方法AddBrushPen();并在AddBrushPen的方法里调用AddPen完成笔表的增加。这么做的考虑是具体类就不用关心笔表的增加操作,也可以避免增加笔表的代码的重复。但现在我有些疑惑了,是不是应该把AddPen做成抽象定义,让子类去各自实现?但这么做的话子类的方法里都会包含重复的向笔表中增加记录的代码,对于我这样看到重复代码就想抽出来替换掉的人来说有些难以接受。

解决方案 »

  1.   

    关于这个继承的数据储存的问题,我想过很多办法,也做过不太成功的试验,的确是一件非常麻烦的事情,因为数据库是不存在继承的。有几种解决方案:
    一、将数据的不同之处包装起来,序列化保存,例如不需要索引的杂项数据,这样,就可以在一张表内保存不同类型的数据。但是如果这些不同的字段需要索引的话,就不能这么做,并且这样比较复杂。二、这个也是我现在正在使用的方法。那就是把数据中相同的,可以抽象出来的字段,作统一相同的命名,基类处理这些抽象的字段,而各个具象子类处理不同的字段。如:
    ParseData( DataRow data )
    {
      base.ParseData( data );//请求基类处理公共部分。
      _xxx = (string) data["xxx"];
    }三、数据层使用代码生成器或者反射映射等方式减少代码。其实这是比较不错的一个方法,但是我暂时还未有尝试。
      

  2.   

    其实数据库和实体的问题一直是个难题,这个问题在以后面向对象的数据库成熟之后应该好解决不过就本题来说,应该不算是太大的难题可以这样解决:class Pen{
       virtual void AddPen();  //需要子类实现
       void AddRealPen()
       {  
         AddToBasicPenTable... //添加到Pen 表
         AddPen();
       }
       }
    然后子类的时候实现 AddPen()
       调用的时候
        Pen brushPen=new BrushPen();
        brushPen.AddRealPen();
      

  3.   

    to Ivony:
      的确凡是使用关系数据库的都会碰到O/R映射问题,好像目前并没有完美的解决方案,我这里采用的是类表继承模式,是一种可行的方案,我看你用的也是这种。你的方法是在子类里写同名的方法屏蔽基类的方法,基类方法用的应该是protect吧?;charles_y的方法是用虚函数,在基类里另外写真正的AddRealPen,用的应该是public;这2种方式效果是一样的,不过我感觉前一种方式能够向下屏蔽基类方法的做法更好,后一种方式的AddRealPen方法则可以被普遍调用。不知道我的理解对不对。
      我的数据层是自动生成的,不过数据库怎样设计是在代码之前,OR映射的问题还是免不了的。to charles_y:
      你写的正是我想要的,不过感觉Ivony的方法更符合OO的思想,比较中......
      

  4.   


    我写的那段代码是非常符合OO思想的,oo中的多态就是这个样子。类似的东西在C++中是使用非常普遍的,而Java,C#等语言提供了另外一种方法,不过已经跟面向对象无关了
      

  5.   

    to Ivony:
      的确凡是使用关系数据库的都会碰到O/R映射问题,好像目前并没有完美的解决方案,我这里采用的是类表继承模式,是一种可行的方案,我看你用的也是这种。你的方法是在子类里写同名的方法屏蔽基类的方法,基类方法用的应该是protect吧?;charles_y的方法是用虚函数,在基类里另外写真正的AddRealPen,用的应该是public;这2种方式效果是一样的,不过我感觉前一种方式能够向下屏蔽基类方法的做法更好,后一种方式的AddRealPen方法则可以被普遍调用。不知道我的理解对不对。
      我的数据层是自动生成的,不过数据库怎样设计是在代码之前,OR映射的问题还是免不了的。to charles_y:
      你写的正是我想要的,不过感觉Ivony的方法更符合OO的思想,比较中......=================================================================================你的理解完全是对的。charles_y(难得糊涂)与我用的其实是一种方法,只不过我令方法的名字是一样的罢了。即我是这样的:public virtual ParseData( DataRow )//避免这个方法被调用很简单,令类是abstract的就行了,这样永远不会有基类的实例。public override ParseData( DataRow )
    他的不过是
    public abstarct AddPen()
    protected AddRealPen()然后在子类调用
    AddRealPen。
    实际上这是没必要的。因为如果你非要名字不同的话,这个类继承几次怎么办?AddRealPen1、AddRealPen2?
      

  6.   

    Ivony() ,这个问题讨论起来也是满有意思的,那我就谈谈我的看法,争取共同进步你可能误解了我的那个方法,我的意思是AddPen()需要子类重写,而AddRealPen是不需要重写的
    不论是 BallPen,还是BrushPen都只需要重载AddPen就行了,AddRealPen()是根本不需要动的,所以不会出现AddRealPen1,AddRealPen2 的情况的。我一般不使用base,当然也不绝对,使用base一般在两个场合,一个是构造函数,另外一个就是"="重载。 "="重载作C++的时候用的比较多,C#还没用过。我个人观点,使用base有两点坏处1 不直观。客户在作重载的时候还得必须记着有个base方法需要加上,这点不太好,给客户限制越少出错的几率就会越小。就这个问题,使用AddPen(),客户只要把东西加进自己关心的表中就可以了,如  
    class BrushPen: public Pen
    {
      override void AddPen()
      {
       加进BrushPen表中
       }

    这样可以较少出错的机会,也比较直观。2 没法实现多态,使用起来不方便。
    设想有这样一个类:
    class Stationery
    {
       Pen  thePen;
       ....
       void AddAllDatabase()
      {
       ....
        thePen.AddRealPen();
      }
    }
    这样在使用的时候,我们可以这样用。
      Stationery theStationery=new Stationery();
        theStationery.thePen=new BrushPen();
       AddAllDatabase();这种时候我们就可以看到,这个AddAllDatabase 就不需要重写,不管是什么Pen都没问题。其实这个时候反射也是一样的,我们可以根据名字来生成一个类
    Pen thePen=(thePen)Assembly.Load("kkksld.dll").CreateInstance("BrushPen");
        thePen.AddRealPen();
        。
    以上是我的一点看法,欢迎讨论
      

  7.   

    我比较喜欢参照微软的做法,.NET Framework中有类似做法。
      

  8.   

    To charles_y:
      看了你的解释,我又仔细比较了2种做法,感觉你说的第一点有道理,这样写可以在写子类的时候不用关心父类,不过实例化对象后,对象却有2个选择AddRealPen和AddPen,使用时还是必须知道要去用AddRealPen,这和在编写子类时要用到base我感觉大同小异。或者说,各有各的好处。至于多态,实际上2种方式都一样可以实现的。-------------------------------
    其实这个例子里还有更复杂的地方,考虑取一条记录出来,那么必须取2张表Pen和BrushPen,假设基类提供public Pen GetPen();那么子类怎么做呢?一方面在这样的情况下基类没法做抽象定义;另一方面,或者子类里可以这样赋值BrushPen realPen= GetPen()?好像不可以吧。
      

  9.   

    只能为基类提供解析的构造函数,如:
    public Pen( DataRow data )
    或者只能由子类调用的不完全构造函数,如:
    protected Pen(){}
    然后子类调用基类的解析数据方法填充基类的数据。