论坛首页 Java企业应用论坛

依赖倒置原则(DIP)批判 -- 称之为本末倒置原则更贴切

浏览 46590 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-10-30  
swing 写道
接着谈
楼主有没有想过依赖倒置的目的,
我想你要批驳它就应该批它的本质,而不是在边边角角挑刺,
因为基本上所有东西都是有依赖环境的(还包括我这个时候在这里说这句话,^_^),因此,如果你通过给依赖倒置换个环境来批驳,基本上我认为等于没说,
比如,我基本上认为依赖倒置是设计级的东西,无论如何,再怎么样,也是有需求先,才好设计,不可能没有需求就搞这么多事情,注意,我这里说的需求包括有时候为了将来考虑而假设的需求,就是说,无论怎么样你要提供过目标或者其它什么的任何东西来说明你需要实现什么,然后我们才来谈设计。

而看楼主你的例子,基本上我的理解是这样的,你想通过描述需求,然后由一个引擎解析并实现或者说执行你的需求脚本来完成系统的建设,
不知道这样理解对不对,反正看你的例子是这样,你唯一不考虑你的解析引擎的复杂度。

好乐回来说正题,我认为依赖倒置的目的是解耦,如果你出于其它目的使用这个原则,我觉得都没有必要好说的乐,当然,如果你遵循这个原则却发现结果并没有达到解耦的目的,那我觉得是因为没有理解并正确使用这个原则。


非常感谢你的大段评论,但是我们还是先关注主题,围绕DIP原则来进行讨论吧。

DIP原则讲的是“要依赖于抽象,不要依赖于具体”,强调“要针对接口编程,不针对实现编程”。

现在来看看我的示例代码,代码中有三个对象:Data、Entity及Action,这三个对象组合在一起实现Insert动作,也就是说它们之间的协作关系完成了对Insert的抽象,这是最高层次的抽象,在关系数据库消失之前它们都不用作任何改动。

再回头看看传统设计,我们有一张数据表用来保存用户资料,你猜人们会如何设计呢?根据他们对面向对象的深刻理解,用户这个“对象”是必不可少的,所以他们的设计八九不离十会是这样:

class User
{
	int	ID;
	string	Name;

	setID();;
	setName();;

	bool Insert();
	{
		string sql = string.Format(" insert user_table values({0},'{1}'); ", ID, Name);;
		return DBAccess.Insert(sql);;
	}
}


如果用户换成雇员的话,class User会换成class Employee,以上的无聊代码同样会反复出现。

讨论到这里,好象跟DIP还是扯不上任何关系,为了展示OO大师的风范,我们还是DIP一下吧。用户和雇员到底有什么关系呢?想来想去好象都是Human,那么DIP终于可以大显身手了。

interface Human
{
	string Name;
	virtual bool Insert();;
}

class User : Human;
class User : Empolyee;


上面的代码看起来是不是觉得很傻,有些人之所以会觉得coding毫无乐趣可言,是否是因为这样的缘故呢?

DIP所追求的方向是正确的,但其方式如果不是无用的,也是不切实际的。
0 请登录后投票
   发表时间:2004-10-30  
我想说:你错了。
你举的例子谈不上DIP,
先看DIP的定义:
1、高层模块不应该依赖于低层模块,二者都应该依赖抽象
2、抽象不应该依赖于细节,细节应该依赖于抽象

你的例子有搭上边吗?

另外,我不会认为因为雇员和用户都是人所以就需要一个人这样的接口来规范它们,继承(或者说扩展实现)是一个很强的关系,有必要才用,虽然在主观上是很显然的is a逻辑,但是你并没有描述这样的需求,那我为什么需要一个接口呢?所以我就是两个简单的值对象类,分别是User、Empolyee,不是也挺好的?(注:这里这么说,完全是因为你没有描述环境或者说需求,所以,其实怎么做都还好,而且你先使用一个接口做扩展,其实是标明乐他们之间的关系的,不过你没有说明环境,所以,这样的设计也许会是多余的)
引用BOB的话:拒绝不成熟的抽象和抽象本身一样重要。

关于DIP,其实你看JAVA的实现好乐,比如XML的解析器实现,SUN给出的是一组接口,有不少的解析器实现都是可以在运行时替换的,你可以根据需要选择,甚至扩展,非常方便。
0 请登录后投票
   发表时间:2004-10-30  
再次强调“我认为依赖倒置的目的是解耦”,
你不管三七二十一就来做抽象,那样的话就导致你的问题失去环境,没有环境,就谈不上解耦,自然会觉得无聊。
我想,你总是应该先考虑你的目的然后才是有针对地设计,不要为了设计而设计,基本原则又不是法律,你根据具体情况可以选择不遵守的。
0 请登录后投票
   发表时间:2004-10-31  
swing 写道
再次强调“我认为依赖倒置的目的是解耦”,
你不管三七二十一就来做抽象,那样的话就导致你的问题失去环境,没有环境,就谈不上解耦,自然会觉得无聊。
我想,你总是应该先考虑你的目的然后才是有针对地设计,不要为了设计而设计,基本原则又不是法律,你根据具体情况可以选择不遵守的。


我批判DIP原则,目的主要是指出它的应用范围实在是小的可怜,也就是其环境适应能力实在是差的不能再差,精心构造DIP的应用环境比DIP应用本身更困难、更有实际性的意义,DIP应用与之相比可以说仅仅是最后的装饰品而已,但奇怪的是DIP这种东西却可以成为指导性的原则,更糟糕的是我们经常会看到玩具级别的例子来展示DIP应用。

举一个例子,我们有一个窗口,上面有两个button,一个负责关闭窗口,一个负责将数据Insert到数据库。两个button的实现的功能完全不同,它们之间可以说完全搭不上任何关系,但是依然可以运用DIP原则,从中可以抽象出Action概念。

class Action
{
	virtual public void OnAction();;
}

class CloseForm : Action
{
	Form form;

	public void setForm();;

	override public void OnAction();
	{
		form.close;		
	}
}

class Insert : Action
{
	string ID;
	string Passwd;
	
	public void setID();;
	public void setPasswd();;

	override public void OnAction();
	{
		string sql = string.Format(" inset user_table values('{0}','{1}'); ", ID, Passwd);;
		DBAccess.Insert(sql);;
	}
}


这是一个典型的DIP应用,绝对符合“高层模块不应该依赖于低层模块,二者都应该依赖抽象,抽象不应该依赖于细节,细节应该依赖于抽象 ”的定义。但是你认为这样的设计能够实现解藕的目的吗?
0 请登录后投票
   发表时间:2004-10-31  
age0 写道
swing 写道
再次强调“我认为依赖倒置的目的是解耦”,
你不管三七二十一就来做抽象,那样的话就导致你的问题失去环境,没有环境,就谈不上解耦,自然会觉得无聊。
我想,你总是应该先考虑你的目的然后才是有针对地设计,不要为了设计而设计,基本原则又不是法律,你根据具体情况可以选择不遵守的。


我批判DIP原则,目的主要是指出它的应用范围实在是小的可怜,也就是其环境适应能力实在是差的不能再差,精心构造DIP的应用环境比DIP应用本身更困难、更有实际性的意义,DIP应用与之相比可以说仅仅是最后的装饰品而已,但奇怪的是DIP这种东西却可以成为指导性的原则,更糟糕的是我们经常会看到玩具级别的例子来展示DIP应用。

举一个例子,我们有一个窗口,上面有两个button,一个负责关闭窗口,一个负责将数据Insert到数据库。两个button的实现的功能完全不同,它们之间可以说完全搭不上任何关系,但是依然可以运用DIP原则,从中可以抽象出Action概念。

class Action
{
	virtual public void OnAction();;
}

class CloseForm : Action
{
	Form form;

	public void setForm();;

	override public void OnAction();
	{
		form.close;		
	}
}

class Insert : Action
{
	string ID;
	string Passwd;
	
	public void setID();;
	public void setPasswd();;

	override public void OnAction();
	{
		string sql = string.Format(" inset user_table values('{0}','{1}'); ", ID, Passwd);;
		DBAccess.Insert(sql);;
	}
}


这是一个典型的DIP应用,绝对符合“高层模块不应该依赖于低层模块,二者都应该依赖抽象,抽象不应该依赖于细节,细节应该依赖于抽象 ”的定义。但是你认为这样的设计能够实现解藕的目的吗?


age0,你的代码很能说明你对于DIP理解的错误,虽然知道其概念,但你至少不了解其深刻的含义。
如果依据你的例子的思路,action的操作需要调用另外一个服务层(Form或者DataAccess)的操作,因为从你的代码中可以看出有需要扩展不同的服务层的需求,那么你应该对具体服务层提供一个抽象,这正是DIP的核心意义所在。

另外你举的这个例子也不是DIP适用的范围,如果设计到swing的action动作,那么更合理的应该使用LIstener或者Observer模式,这样更适合于松耦合,易于扩展!
0 请登录后投票
   发表时间:2004-10-31  
age0,我想问你,你新举的例子和你前面的例子有没有本质区别?如果没有,我已经说你前面的例子不能算什么dip乐,你居然还新拿这个来和我们说“这是一个典型的DIP应用”,我只能再说一次,给你打0分。
我这么说,真的不是我瞎掰的。
另外我发现你是不会读的,估计是拷贝粘贴用多乐,
引用:绝对符合“高层模块不应该依赖于低层模块,二者都应该依赖抽象,抽象不应该依赖于细节,细节应该依赖于抽象 ”的定义。

双引号打得是漂亮,可惜你没有读里面的内容。

下面如果你有心,那么跟着我再读一次吧:
“高层模块不应该依赖于低层模块”
好了,我们只读这么一点就好了,
我想问你,你举的例子中,哪个部分是高层,哪个部分是低层?
别告诉我你的接口是高层,实现类是低层,分层的概念我就不再去找教科书给你贴乐。
其它部分我就先不仔细解释乐,你先理解乐这个,咱们才好接着讨论。
0 请登录后投票
   发表时间:2004-10-31  
还是前面关于XML解析器的实现,
我们来深入一步好了,
假设你现在准备用一个实现乐org.w3c.dom 包的DOM实现,比如xerces,
这个时候你会去直接使用xerces中的Document接口实现类,还是说你通过org.w3c.dom提供的Document接口?我想是没有人关心xerces中是怎么实现Document接口的,甚至都不需要去关心到底那个实现类类名叫什么,你只要都依赖org.w3c.dom提供的接口,那么将来你就可以替换一个实现,比如出现了一个新的实现,它的效率是xerces的100000000倍,呵呵这个效率实在是难以抵抗的诱惑,你想替换,这个时候,如果你的代码(这里就是高层乐,高层是相对低层而言的)总是通过org.w3c.dom包的接口(这个包就是上面你所引用的一句话中的“抽象”乐)引用的,那么你只管替换实现包(这个就是低层实现乐)好了,你的代码不需要做任何改动,
但是你如果在你的代码中使用乐xerces实现中所独有的类,比如DocumentImp之类(注,我忘了那个实现类类名乐,差不多是这样大概,这里就不用较真乐我想),你就需要改你的代码乐。
到底是不是解耦乐,两个字:
显然
0 请登录后投票
   发表时间:2004-11-01  
解藕有很多方式,所谓的高层模块、低层模块、接口类、实现类不过是建筑在oo概念之上的条条框框而已,跳不出这些束缚的话coding只是自虐而已。

举个容易理解的例子。哺乳动物基本上都会发声,猫的叫声是“喵~~”,狗的叫声是“汪~~”,那么看看我们可以如何组织Animal,Cat和Dog之间的关系。

标准的DIP方式。
class Animal
{
	virtual public void Bark();;
}

class Cat : Animal
{
	override public void Bark();
	{
		print("喵~~");;
	}
}

class Dog : Animal
{
	override public void Bark();
	{
		print("汪~~");;
	}
}

client:
Animal cat = New Cat();;
Animal dog = New Dog();;
cat.Bark();;
dog.Bark();;



非DIP方式。
class Animal
{
	string voice = "";
	
	public void setVoice(string v);;

	public void Bark();
	{
		print(voice);;
	}
}

class Animals
{
	static public Animal Cat();
	{
		Animal cat = new Animal();;
		cat.setVoice("喵~~");;
		
		return cat;
	}
	
	static public Animal Dog();
	{
		Animal dog = new Dog();;
		dog.setVoice("汪~~");;
		
		return dog;
	}
}

client:
Animal cat = Animals.cat;
Animal dog = Animals.dog;
cat.Bark();;
dog.Bark();;
0 请登录后投票
   发表时间:2004-11-01  
服了,age0,
你新的例子和你前面的例子仍然没有本质区别,
何况,我早说了,象你举的这些例子,根本没有dip不dip的。
我说飞机速度快,从北京到上海做飞机比较好,
你却把飞机扔到海里,然后说,我看飞机也不过如此,那么笨重,浮起来就不错乐,
还不如我自己扎个木筏划得快。
4 请登录后投票
   发表时间:2004-11-01  
觉得Age0的例子和DIP根本没啥联系,这能叫DIP啊
只能说.....  WO ... FT
Swing说的那都是的的确确的东西,我看Age0根本没体会啥叫DIP就跑出来起哄
你那个例子:
public abstract class Animal {
    
    public abstract String getVoice();;
    
    public void bark(); {
        System.out.println(getVoice(););;
    }
}

public class Cat extends Animal {

    public String getVoice(); {
        return "Miao";
    }

}

public class Dog extends Animal {

    public String getVoice(); {
        return "Wang";
    }

}

这我觉得才能叫DIP
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics