面向对象思想的头脑风暴(一)

团队中对面向对象的理论研究已经做了很长时间,大家对接口,封装,继承,多态以及设计模式什么的似乎都能说出点东西来,但当看代码时发现大家其实并不十分清楚具体怎么做,所以我就想了个题目让大家来做,然后进行了一次头脑风暴,过程记录如下:

题目内容:

需要处理三种产品图书,数码,消费,需要计算产品的税率,图书的税率为价格的0.1,数码和消费类产品为价格的0.11,需要获得三种产品的信息,图书和消费类产品的信息为:"名字:" + Name;,数码产品的信息为:"数码类名字:" + Name;

要求:符合ocp原则(不懂ocp原则的到网上去查,变化点在计税的方式可能改变,信息打印方式可能改变)

这里我给大家一个面向过程的版本,方便大家理解需求

 

ExpandedBlockStart.gif 代码
  public   class  Product
    {
        
public   string  Name {  get set ; }
        
public   double  Price {  get set ; }
        
public   int  Type {  get set ; }
        
public   string  GetProductInfo()
        {
            
switch  (Type)
            {
                
case   1 :
                
case   2 :
                    
return   " 名字: "   +  Name;
                
case   3 :
                    
return   " 数码类名字: "   +  Name;
                
default :
                    
return   string .Empty;
            }
        }
        
public   double  ComputeTax()
        {
            
switch  (Type)
            {
                
case   1 :
                    
return  Price  *   0.1 ;
                
case   2 :
                
case   3 :
                    
return  Price  *   0.11 ;
                
default :
                    
return  Price;
            }
        }
    }

 

测试代码

 

ExpandedBlockStart.gif 代码
[TestMethod()]
        
public   void  GetProductInfoTest()
        {
            Product book 
=   new  Product() { Name  =   " C#编程 " , Price  =   50 , Type  =   1  };
            Product consume 
=   new  Product() { Name  =   " 桌子 " , Price  =   100 , Type  =   2  };
            Product digital 
=   new  Product() { Name  =   " 数码相机 " , Price  =   1000 , Type  =   3  };
             
            Assert.AreEqual(
" 名字:C#编程 " , book.GetProductInfo());
            Assert.AreEqual(
" 名字:桌子 " , consume.GetProductInfo());
            Assert.AreEqual(
" 数码类名字:数码相机 " , digital.GetProductInfo());

        }

 

这个过程化的版本的问题如下:

1、当更改税率或获得信息时需要修改product类,这不符合ocp原则

2、product类的职责也太多了

当然如果就只是这么简单的需求的话,这个过程化的版本也不错,至少简单。

第一个方案:

 

ExpandedBlockStart.gif Product类
  class  Product
    {
        
public   string  Name 
        {
            
get  {  return  name; }
            
set  { name  =  value; } 
        }
        
private   string  name;

        
public   double  Price 
        {
            
get  {  return  price; }
            
set  { price  =  value; }  
        }
        
private   double  price;

        
public   double  TaxRate 
        {
            
get  {  return  taxRate; }
            
set  { taxRate  =  value; } 
        }
        
private   double  taxRate;

        
public   int  Type
        {
            
get  {  return  type; }
            
set  { type  =  value; }
        }
        
private   int  type;

        
public  Product( string  name, double  price, double  taxRate, int  type)
        {
            
this .Name  =  name;
            
this .Price  =  price;
            
this .TaxRate  =  taxRate;
            
this .Type  =  type;
        }

        Tax tax 
=   new  Tax();

        
public   double  GetTax()
        {
            
return  tax.ComputeTax(price,taxRate);
        }

        ProductInfo productInfo 
=   new  ProductInfo();

        
public   string  GetProductInfo()
        {
            
return  productInfo.GetProductInfo(type, name);
        }
    }

 

 Tax类

 

  class  Tax
    {
        
public   double  ComputeTax( double  price,  double  taxRate)
        {
            
return  price  *  taxRate;
        }
    }

 

 

 

ExpandedBlockStart.gif ProductInfo
class  ProductInfo
    {
        
public   string  GetProductInfo( int  type, string  name)
        {
            
switch  (type)
            {
                
case   1 :
                    
return   " 名字: "   +  name;
                
case   2 :
                    
return   " 数码类名字: "   +  name;
                
default :
                    
return   string .Empty;
            }
        }
    }

 

这个方案其实完全没有解决问题,而且还比原来的方案更复杂了。新的税率仍然要改代码,新的信息仍然要改代码。

第二个方案:

 

ExpandedBlockStart.gif Product
public   class  Product
    {
       
        
public   delegate   double  GetTax( double  price);
        
public  GetTax gt;

        
public   string  Name
        {
            
get ;
            
set ;
        }
        
public   int  type
        {
            
get ;
            
set ;
        }
        
public   double  Price
        {
            
get ;
            
set ;
        }

        
public   double  ComputeTax()
        {
            
return  gt( this .Price);
        }

        
public   override   string  ToString()
        {
            
return   base .ToString();
        }
    }

 

 

 

ExpandedBlockStart.gif CreateProduct
class  CreateProduct
    {
        
static  Product pp;
        
public   static  ITax Tax
        {
            
get ;
            
set ;
        }
        
public   static  IPrint print
        {
            
set ;
            
get ;
        }
        
public    static   Product PProduct
        {
            
get
            {
               
                
return  pp;
                
            }
            
set  { pp  =  value; pp.gt  =  Tax.CoumputeTax; }
        }
        
    }

 

 

interface  ITax
    {
        
double  CoumputeTax( double  price);
    }

 

ExpandedBlockStart.gif Main
static   void  Main( string [] args)
        {

            Product book 
=   new  Product();
            book.Name 
=   " 图书 " ;
            book.Price 
=   1 ;
            book.type 
=   1 ;

            
            CreateProduct.Tax 
=   new  BookTax();
            CreateProduct.PProduct 
=  book;
             
            Console.Write(
" this product name is {0}, price is {1} " , book.Name,book.Price);

            Console.Read();
        }

 

这个方案似乎是解决了问题,新的税率变化添加一个新类继承ITax接口,在客户端用即可。但这种方案显然属于知识学太多了,混用了委托与接口,无端的增加的复杂度,CreateProduct完全没有存在的必要。

第三种方案:

 

ExpandedBlockStart.gif Product类
  class  Product
    {
        
public   delegate   double  DComputeTax( double  price);
        
public  DComputeTax dComputeTax;
        
public   string  Name {  get set ; }
        
public   double  Price {  get set ; }
        
public   int  Type {  get set ; }
        
public   string  GetProductInfo()
        {

            
return   "" ;
        }
        
public   double  ComputeTax()
        {
            
if  (dComputeTax != null )
            {
               
return  dComputeTax(Price);
            }
  
            
return   0 ;
        }


    }

 

 

ExpandedBlockStart.gif 代码
[TestMethod()]
        
public   void  ComputeTaxTest()
        {
            ProductN.DComputeTax d 
=   new  ProductN.DComputeTax(c => c * 0.1 );

            ProductN target 
=   new  ProductN() { Price  =   100  };  //  TODO: 初始化为适当的值
            target.dComputeTax  =  d;
            
double  expected  =   10 //  TODO: 初始化为适当的值
             double  actual;
            actual 
=  target.ComputeTax();
            Assert.AreEqual(expected, actual);

        }

 

这是使用委托来实现的方案,我觉得这个方案符合了需求,也符合了ocp原则,但和接口方案还是有区别的,我们先看看接口组合方案:

第四种方案:

 

ExpandedBlockStart.gif 代码
class  Product
    {
        
public   string  Name {  get set ; }
        
public   double  Price {  get set ; }

        IGetInfo getInfo;
        IGetTax getTax;
        
public  Product(IGetInfo getInfo, IGetTax getTax)
        {
            
this .getInfo  =  getInfo;
            
this .getTax  =  getTax;
        }
        
public   string  GetInfo()
        {
            
return  getInfo.GetInfo(Name);
        }
        
public   double  GetTax()
        {
            
return  getTax.GetTax(Price);
        }
    }
interface  IGetTax
    {
        
double  GetTax( double  price);
    }
interface  IGetInfo
    {
        
string  GetInfo( string  name);
    }
 
class  BookTax:IGetTax
    {
        
#region  IGetTax 成员

        
public   double  GetTax( double  price)
        {
            
return  price * 0.1 ;
        }

        
#endregion
    }
class  ConsumTax:IGetTax
    {
        
#region  IGetTax 成员

        
public   double  GetTax( double  price)
        {
            
return  price * 0.11 ;
        }

        
#endregion
    }
 
class  DigitalInfo:IGetInfo
    {
        
#region  IGetInfo 成员

        
public   string  GetInfo( string  name)
        {
            
return    " 数码类名字: "   +  name;
        }

        
#endregion
    }
class  SampleInfo:IGetInfo
    {
        
#region  IGetInfo 成员

        
public   string  GetInfo( string  name)
        {
            
return   " 名字: " + name;
        }

        
#endregion
    }

 

 

测试代码

 

ExpandedBlockStart.gif 代码
 [TestMethod]
        
public   void  GetInfoTest()
        {
            Product book 
=   new  Product( new  SampleInfo(),  new  BookTax()) { Name  =   " C#编程 " , Price  =   50  };
            Assert.AreEqual(
" 名字:C#编程 " , book.GetInfo());
            Assert.AreEqual(
5 , book.GetTax());
            Product consume 
=   new  Product( new  SampleInfo(),  new  ConsumTax()) { Name  =   " 桌子 " , Price  =   100  };
            Assert.AreEqual(
" 名字:桌子 " , consume.GetInfo());
            Assert.AreEqual(
11 , consume.GetTax());
            Product digital 
=   new  Product( new  DigitalInfo(),  new  ConsumTax()) { Name  =   " 数码相机 " , Price  =   1000  };
            Assert.AreEqual(
" 数码类名字:数码相机 " , digital.GetInfo());
            Assert.AreEqual(
110 , digital.GetTax());

        }

 

我觉得对于这个需求来说方案三和方案四都应该算是符合要求的比较好的解决方案,这两种方案各有优缺点。

 方案三(委托方案)的优点:

1、足够灵活

2、代码简单,类少

缺点:

1、缺乏限制,只要符合计税委托签名的方法就可以计算税率,往往会造成已实现的业务代码职责不够清晰。

方案四(接口方案)的优点:

1、职责明确

2、也足够灵活

缺点:

1、使用的类往往过多

 

接口和委托的区别:

接口(interface)用来定义一种程序的协定。实现接口的类或者结构要与接口的定义严格一致。接口(interface)是向客户承诺类或结构体的行为方式的一种合同,当实现某个接口时,相当于告诉可能的客户:“我保证支持这个接口的方法,属性等”,接口不能实例化,接口只包含成员定义,不包含成员的实现,成员的实现需要在继承的类或者结构中实现。
C#中的委托是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相同的行为,委托方法的使用可以像其他任何方法一样具有参数和返回值。委托对象能被传递给调用该方法引用的代码而无须知道哪个方法将在编译时被调用。
从定义上来看似乎委托和接口没什么相似之处,但从隔离变化这个角度来看他们倒是有些相似之处,所以这里我们把他们放到一起来比较一番。

委托和接口都允许类设计器分离类型声明和实现。给定的接口可由任何类或结构继承和实现;可以为任何类中的方法创建委托,前提是该方法符合委托的方法签名。接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢?

在以下情况中使用委托:

当使用事件设计模式时。委托是事件的基础,当需要某个事件触发外界响应时,使用委托事件比较合适。

当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。

需要方便的组合,使用委托可以利用+=,-=方便的组合方法。

当类可能需要该方法的多个实现时,使用多播委托。

在以下情况中使用接口:

当存在一组可能被调用的相关方法时。

当类只需要方法的单个实现时。

当使用接口的类想要将该接口强制转换为其他接口或类类型时。

当正在实现的方法链接到类的类型或标识时:例如比较方法。

使用单一方法接口而不使用委托的一个很好的示例是 IComparable 或 IComparable。IComparable 声明 CompareTo 方法,该方法返回一个整数,以指定相同类型的两个对象之间的小于、等于或大于关系。IComparable 可用作排序算法的基础,虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想。因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一方法接口是理想的。

你可能感兴趣的:(面向对象思想的头脑风暴(一))