实现有价值的IT服务
全部博文(709)
分类: IT职场
2006-07-21 10:22:04
abstract class Demo { abstract void method1(); abstract void method2(); … } |
interface Demo { void method1(); void method2(); … } |
使用abstract class方式定义Door:abstract class Door { abstract void open(); abstract void close(); } 使用interface方式定义Door: interface Door{ void open(); void close(); } |
abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。
理解抽象类
abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?
在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。
从语法定义层面看abstract class和interface
在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。
使用abstract class的方式定义Demo抽象类的方式如下:
abstract class Demo {
abstract void method1();
abstract void method2();
…
}
使用interface的方式定义Demo抽象类的方式如下:
interface Demo {
void method1();
void method2();
…
}
在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。
对于abstract class和interface在语法定义层面更多的细节问题,不是本文的重点,不再赘述,读者可以参阅参考文献〔1〕获得更多的相关内容。
从编程层面看abstract class和interface
从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。
首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。
其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。
在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。
同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。
从设计理念层面看abstract class和interface
上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。
前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。
考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:
使用abstract class方式定义Door:
abstract class Door {
abstract void open();
abstract void close();
}
使用interface方式定义Door:
interface Door {
void open();
void close();
}
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。
如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。
解决方案一:
简单的在Door的定义中增加一个alarm方法,如下:
abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}
或者
interface Door {
void open();
void close();
void alarm();
}
那么具有报警功能的AlarmDoor的定义方式如下:
class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}
或者
class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }
}
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。
解决方案二:
既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。
显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:
abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。
结论
abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。
深入理解abstract class和interface
interface与多重继承的观念
不管是Java的interface或是C++的多重继承﹐在物件导向的理论里﹐都算是蛮新颖的概念。所以这里我们谈的﹐是以程式语言的角度﹐看看Java interface的所有意义与功能﹐是否C++的多重继承能全部诠释?或是相反地以Java的来诠释C++的。
首先让我们来复习一下什么是C++的多重继承。 「继承」通常在物件导向程式语言中﹐扮演着程式码的重复利用的重责大任﹐而C++的多重继承则让某一个子类别可以继承许多分属于不同资料型别的父类别如下:
#include class Test1 {
public:
virtual void f1() {puts("Test1::f1()"); }
virtual void g1() {puts("Test1::g1()"); }
};
class Test2 {
public:
virtual void f2() { puts("Test2::f2()"); }
virtual void g2() { puts("Test2::g2()"); }
};
class Test3 : public Test1, public Test2 {
public:
virtual void gg() { puts("Test3::gg()"); }
};
void main() {
Test3 t3; t3.f1(); t3.f2();
t3.g1(); t3.g2(); t3.gg();
}
// 程式输出:
Test1::f1() Test2::f2() Test1::g1()
Test2::g2() Test3::gg()
程式1﹑C++的多重继承
根据[Rie96]﹐认为正确使用物件导向技术中之「多重继承」观念﹐应该如下面的例子:
假设有一个木造门﹐则:
1. 此木造门是门的一种(a kind of)。
2. 但门不是木造门的一部份(a part of)。
3. 木造门是木制品的一种。
4. 但木制品不是木造门的一部份。
5. 木制品不是门的一种。
6. 门也不是木制品的一种。
所以您可以发现﹐多重继承在使用时﹐必须非常小心﹐而且在许多时候﹐其实我们并不需要多重继承的。
Java也提供继承机制﹐但还另外提供一个叫interface的概念。由于Java的继承机制只能提供单一继承(就是只能继承一种父类别)﹐所以就以Java的interface来代替C++的多重继承。interface就是一种介面﹐规定欲沟通的两物件﹐其通讯该有的规范有哪些。如以Java程式语言的角度来看﹐Java的interface则表示:一些函数或资料成员﹐为另一些属于不同类别的物件所需共同拥有﹐则将这些函数与资料成员﹐定义在一个interface中﹐然后让所有不同类别的Java物件可以共同操作使用之。
所以﹐对于Java的继承与interface﹐我们总结如下:
1.Java的class只能继承一个父类别(用extends关键字)﹐但可以拥有(或称实作)许多interface(用implements关键字)。
2.Java的interface可以继承许多别的interface(也是用extends关键字)﹐但不可以实作任何interface。
因此﹐我们可以利用Java的interface来模拟C++的多重继承。如上面的例子可以转化如下:
interface Test1 {
public void f1();
public void g1();
}
interface Test2 {
public void f2();
public void g2();
}
interface Test3 extends Test1, Test2 {
public void gg();
}
class CTest implements Test3 {
public void f1() { System.out.println("Test1::f1()"); }
public void g1() { System.out.println("Test1::g1()"); }
public void f2() { System.out.println("Test2::f2()"); }
public void g2() { System.out.println("Test2::g2()"); }
public void gg() { System.out.println("Test3::gg()"); }
}
class Run {
public void run() {
CTest ct=new CTest(); ct.f1();ct.f2();
ct.g1();ct.g2(); ct.gg();
}}
class Main {
public static void main (String args[]) {
Run rr=new Run();
rr.run();
}}
// 程式输出:
Test1::f1() Test2::f2() Test1::g1()
Test2::g2() Test3::gg()
程式2﹑利用Java的interface完成C++的多重继承功能
然而﹐根据[Ait96]的文章显示﹐他认为Java的interface比C++的多重继承好学很多﹐也较容易懂﹐但是有其限制。对于Java interface的易懂﹐在文章中﹐并没有说明。其主要即为「介面继承」与「实作继承」概念的差异。
「介面继承」就是只继承父类别的函数名称﹐然后子类别一定会实作取代之。所以当我们以父类别的指标「多型」于各子类别时﹐由于子类别一定会实作父类别的多型函数﹐所以每个子类别的实作都不一样﹐此时我们(使用父类别指标的人)并不知道此多型函数到底怎么完成﹐因之称为「黑箱设计」。
「实作继承」就是继承父类别的函数名称﹐子类别在实作时﹐也会用到父类别的函数实作。所以我们(使用父类别指标的人)知道此多型函数怎么完成工作﹐因为大概也跟父类别的函数实作差不多﹐因之称为「白箱设计」。
套用的Java的interface上﹐我们发现﹐Java的interface就是介面继承﹐因为Java interface只能定义函数名称﹐无法定义函数实作﹐所以子类别必须用「implements」关键字来实作之﹐且每个实作同一介面的子类别当然彼此不知道对方如何实作﹐因此为一个黑箱设计。
Java的类别继承则为实作继承﹐子类别会用到父类别的实作(更正确地说应该是父类别有定义实作﹐所以子类别可能会使用到﹐即使不使用到也会遵循父类别实作的演算法)﹐所以父类别与子类别有一定程度的相关性﹔不像介面继承﹐彼此只有函数名字刚好一样而已。
介面继承与实作继承﹐应对至Java的interface﹑class﹑extends与implements关键字﹐很容易了解其含意。但是C++的继承机制﹐似乎就没有那么容易解释清楚的!所以这就是[Ait86]文章中所表示的意思:C++多重机制比较复杂。
所以接下来我们将讨论:
C++的多重继承有什么功能﹐是Java的interface所达不到的? #include class t1 {
public:
virtual void f() { puts("t1::f()"); }
virtual void g() { puts("t1::g()"); }
};
class t2 : public virtual t1 {
public:
virtual void g() { puts("t2::g()"); }
};
class t3 : public virtual t1 {
public:
virtual void f() { puts("t3::f()"); }
};
class t4 : public t2, public t3, public virtual t1 { ...};
void main() {
t4 *tt4=new t4; t2 *tt2=tt4; t3 *tt3=tt4;
tt4->f(); tt4->g(); tt2->f(); tt3->g();
}
// 程式输出:
t3::f() t2::g() t3::f() t2::g()
程式3﹑C++著名的环状继承
由上例﹐我们发现﹐C++的多重继承具有下列两个特质是Java的interface所不能达到的功能。
图1﹑C++的环状多重继承
C++的多重继承可以形成环状继承关系﹐如图1。但是不管是Java的继承机制或是interface﹐都不容许有环状的情况发生。换句话说﹐因为C++有virtual base的属性的父类别﹐所有在多重继承时﹐允许父类别被继承两次以上。但Java则完全不行。
本题中的tt4指标﹐转成tt2指标后﹐执行f()函数时﹐仍然会正确地执行tt4中的f()函数﹐也就是t3::f()。我们可以发现﹐这种找函数的方式﹐是先找函数的正确名称﹐再找函数所属的类别的正确名称。与Java的虚拟函数(或称为abstract函数)不同。Java的是先找指标(或参考)所属的正确类别名称﹐再继续找类别名称下的正确函数名称。
图2﹑对于虚拟函数C++与Java的各别作法
对于第二点参考图2。C++的虚拟函数﹐可以参考[Sou94]﹐C++编译器对于每一个虚拟函数﹐均建立一个虚拟函数表与之应对﹐因为每一个虚拟函数在一个继承树可能有许多子类别实作之。因此在实际执行时﹐是先找虚拟函数表﹐然后再寻找与自己类别阶层等级最靠近的那个函数实作。所以我们可以将一个父类别指标转换成子类别后﹐反而仍执行母亲那辈的虚拟函数。
而Java则不能透过interface执行上述功能。Java的抽象函数(abstract function)事实上就是C++的纯虚拟函数(virtual function()=0)﹐没有像C++可以有非纯虚拟函数(就是子类别不一定要定义的虚拟函数)﹐所以很难执行上面复杂的例子。并且﹐Java的interface里的函数预设为抽象函数﹐也就是如果某类别实作此interface的话﹐interface里的所有函数都必须全部实作。因此在实际执行时﹐先决定interface物件转型成某类别的物件﹐于是再执行该类别内的函数实作。
乍看之下﹐似乎C++的多重继承功能较完整﹐那么:
Java的interface概念﹐是否可用C++的多重继承模拟出来呢? class CoSomeObject : public Iunknown, public Ipersiet {
// IUnknown methods
virtual DWORD AddRef(void);
virtual DWORD Release(void);
virtual HRESULT QueryInterface(REFILD, LPVOID FAR*);
//IPersist methods
virtual HRESULT GetCLASSID(LPCLSID pclsid);
};
(注:参见MFC Internals p.p 442)
然而﹐考虑以下的问题:Java interface的特色为﹐interface间可以继承。但这里所谓的继承﹐事实上只是子interface包括全部的父interface内的成员﹐无法像Java的类别或是C++的类别那样﹐可以用子类别的新定义将父类别取代之。这也就是所谓的「介面继承」与「实作继承」的差别。换言之﹐Java interface继承(注意﹐不是子类别implements父interface)只是一种「包含关系」而已﹐甚至包含不可以重复。所以﹐interface概念用在软体模组间的介面定义便非常厉害﹐如CORBA的IDL便是。因此﹐Java interface仍然拥有许多优点﹐但如果我们要以C++的多重继承模拟(或称之为藉此学习Java interface概念)时﹐在C++这边该如何映对呢?
我们以程式2的Java程式为主﹐观察C++的模拟interface版﹐该如何映对。
#include class Test1 {
public:
virtual void f1()=0;
virtual void g1()=0;
};
class Test2 {
public:
virtual void f2()=0;
virtual void g2()=0;
};
class Test3 : public Test1, public Test2 {
public:
virtual void gg()=0;
};
class CTest : public Test3 {
public:
void f1() { puts("Test1::f1()"); }
void g1() { puts("Test1::g1()"); }
void f2() { puts("Test2::f2()"); }
void g2() { puts("Test2::g2()"); }
void gg() { puts("Test3::gg()"); }
};
void main() {
CTest ct; ct.f1();ct.f2();
ct.g1();ct.g2(); ct.gg();
}
// 程式输出:
Test1::f1() Test2::f2() Test1::g1()
Test2::g2() Test3::gg()
程式4﹑C++对应的Java interface概念
由程式4可知﹐其实只要在每个类别中的每一个函数都宣告成纯虚拟函数﹐则C++类别就变成Java interface概念了。
因此我们下一结论:C++的多重继承功能较广﹐Java的interface功能只是其中的一个子集。因为C++的虚拟函数可以有纯虚拟函数﹐也可有非纯虚拟函数﹐而Java只有抽象函数﹐所以功能模式少一种﹐自然能达到的效果较少一些。
但这并不代表Java的interface就比较差﹐因为interface的观念较简单﹐全部动态的抽象函数也正代表着Java为一纯物件导向语言。与C++不同的是﹐C++考虑许多执行效率的问题﹐所以语言本身就变的较复杂化﹐同时C++的编译器也是公认难写的﹐多重继承更是一大挑战。Java的interface虽然好象少了一些功能﹐但其实那些功能在一般的程式设计中也很少碰到﹔并且观念简洁的Java interface﹐在设计软体程式时更可以避免许多陷阱。总之﹐两种程式语言机制是很难比较谁好谁坏的。
顺带一提的是:最近有一股新趋势﹐就是物件导向程式语言所引导出来的新的概念﹐反过来引响物件导向分析与设计的基础理论。如Java的interface或是微软的OLE Interface﹐使得Booch﹑OMT整合方法UML﹐对软体模组间的介面有了新的定义﹔或是C++的STL与template﹐促进许多学者对静态的物件关系又有了新解等。■
参考资料 [Fla96]Flanagan, David. (1996) Java in a Nutshell, O’Reilly.
[Mor+96]Morrison, Michael. et al. (1996) Java Unleashed., Sams.
[Rie96]Reil, Arthur. (1996) Object-Oriented Design Heuristics, Addison-Wesley.
[[Str94]Stroustrup, Bjarne. (1994) The Design and Evolution of C++, Addison-Wesley.
文章来自
在C++的ARM中﹐或是[Str94]的多重继承章节里﹐皆提到了下述著名的例子:
目前﹐在微软的OLE技术中﹐确实是用C++的多重继承模拟OLE中的interface概念﹐可以参考MFC中关于OLE COM的写作。如下面的程式:
[Ait96]Aitken, Gary. (1996) Moving from C++ to Java, Dr.Dobb’s Journal, Vol.21, Issue 3, A Miller Freeman Publication.
java中的类类似于c中的指针唯一的区别是你不能像c中那样去操作它,new运算符在运行期间为对象分配内存,因此运行一次new,就分配一块内存区域。假如有两个类A和B,
public class A {
method1();{}
method2();{}
method3();{}
method4();{}
}
类B只使用method1()则
public class B {
A a=new A();
a.method1();
}
因此运行一次new,就开辟了一块内存区域(即使不使用method2() method3() method4()也开辟与之对应的内存区域),所以存在耦合
如果有了接口就不这样了,可以根据接口灵活调用类中的方法,降低了耦合。
我觉得这才是java中的接口产生的最根本的原因和最根本的作用
sakulagi 回复于:2005-09-06 07:34:41
即使有了interface,也还是实例化同样的对象,并没有减少内存的使用
另外method()占用的内存和new没有关系,无论是c还是java都不会在每次new的时候都给某个方法或是函数分配空间的,那不是太浪费了么?
TAxxjszxlkjf 回复于:2005-09-06 17:13:54
public interface callback {
public void method1();
}
public class A {
method1();{}
method2();{}
method3();{}
method4();{}
}
public class B {
callback a=new A();
a.method1();
}
如果不使用接口B要调用A的方法, 会将A当作参数传给B,然后B再调用A的方法method1(),如果使用接口,则B在调用A的方法时候直接调用method1()方法即可。
(如果我的理解不正确,求求大哥版主了,给我一个接口可以降低耦合的实例好吗)
dennis2 回复于:2005-09-06 23:44:23
public class A {
应该是
public class A implements callback {
才对。这样你才能做 callback a = new A();
接口是面向对象编程的很重要的概念之一,它定义了调用与被调用方的 Contract。你可以完全替换掉一个接口的实现,而你只需在调用方改动一个语句。比如,你现在又有一个类实现 callback 这个接口:
class C implements callback {
method1() {
// an entirely different implementation
}
}
在 class B 里面,你只需把
callback a=new A();
替换成
callback a = new C();
后面的语句不用改,那么整个 callback 的实现就变成 class C 的实现了。
TAxxjszxlkjf 回复于:2005-09-07 00:36:26
不好意思粘贴错了应该是
public interface callback {
public void method1();
}
public class A implements callback {
method1();{}
method2();{}
method3();{}
method4();{}
}
public class B {
callback a=new A();
a.method1();
}
sakulagi 回复于:2005-09-07 07:34:05
dennis2讲的是对的,这样其实就是在降低耦合度
TAxxjszxlkjf 回复于:2005-09-07 11:22:29
public class A {
method1();{}
method2();{}
method3();{}
method4();{}
}
class C {
method1() {
// an entirely different implementation
}
}
public class B {
A a=new A();
a.method1();
}
把A a=new A() 改为C a=new C()不就行了吗
怎么体现降低耦合度呢
callback a=new A() 和 A a=new A()
是不是后者直接把类当参数传递前者可以直接调用方法
tinywind 回复于:2005-09-07 11:31:43
因为你用A a=new A()的时候,还是知道A是一个具体的实现,所以你没法解耦,解决方法有很多。作为参数传入是一种,利用factory模式创建也可以,现在时髦的IOC也是一种方式。
TAxxjszxlkjf 回复于:2005-09-07 13:44:42
楼上朋友 我的意思是说A a=new A() 改为C a=new C()是不是降低了耦合?
tinywind 回复于:2005-09-07 14:13:24
当然不是,你这种情况就是强耦合,也就是我们要避免地情况
TAxxjszxlkjf 回复于:2005-09-07 15:43:37
楼上的朋友
callback a=new A() 和 A a=new A()
那么这样的方式(使用接口)是不是就降低了耦合呢
tinywind 回复于:2005-09-07 16:22:04
如果class A是interface callback的一个实现的话,对于调用者来说A就是透明的,所以不应该有new A()这种操作存在
至于调用者怎么获得interface的实现,可以参考我前面回帖中说的方法
sakulagi 回复于:2005-09-07 16:35:24
楼主的代码上还是没有降低耦合度。
我举个别的例子吧:
[code:1:3652374f12]
import java.util.Iterator;
import java.util.Vector;
//Interface Decouble Example
public class ChinaUnixJavaCircusShow{
public static void main(String[] args) {
Circus c = new Circus();
c.addAnimalActors(new Bear());
c.addAnimalActors(new Horse());
c.addAnimalActors(new Dog());
c.bigShow();
}
}
interface AnimalActor {
public void showBestPose();
}
class Bear implements AnimalActor {
public void showBestPose() {
System.out.println("This bear show!!");
}
}
class Horse implements AnimalActor {
public void showBestPose() {
System.out.println("This horse show!!");
}
}
class Dog implements AnimalActor {
public void showBestPose() {
System.out.println("This dog show!!");
}
}
class Circus {
private Vector actors = new Vector();
public void addAnimalActors(AnimalActor a) {
actors.add(a);
}
public void bigShow() {
Iterator i = actors.iterator();
while (i.hasNext()) {
((AnimalActor) i.next()).showBestPose();
}
}
}
[/code:1:3652374f12]
由于使用了接口,Circus不再理会参加演出的具体的动物演员的类,只要是实现了AnimalActor接口的类都可以参加Big Show。
如果没有使用接口,那么Circus的代码中就要包含Horse,bear, dog类的引用。现在Circus的代码和具体可能有多少种动物无关,这就是解藕
dennis2 回复于:2005-09-07 21:45:03
版主说得没错,例子很形象 :)
我原本也想回这个贴,但一时没有找到好的例子。
TAxxjszxlkjf 回复于:2005-09-08 08:30:30
这种例子我也是知道的
类F
package com.examples.basics;
interface wuqi{
public void gongji();
}
class A implements wuqi {
public void gongji() {
System.out.println("Inside A's constructor.");
}
}
class B implements wuqi {
public void gongji() {
System.out.println("Inside B's constructor.");
}
}
class C implements wuqi {
public void gongji() {
System.out.println("Inside C's constructor.");
}
}
class F {
public static void main(String args[]){
wuqi a = new A();
a.gongji();
wuqi b = new B();
b.gongji();
wuqi c = new C();
c.gongji();
}
}
类Epackage com.examples.basics;
class A {
public void gongji() {
System.out.println("Inside A's constructor.");
}
}
class B {
public void gongji() {
System.out.println("Inside B's constructor.");
}
}
class C {
public void gongji() {
System.out.println("Inside C's constructor.");
}
}
class E {
public static void main(String args[]){
A a = new A();
a.gongji();
B b = new B();
b.gongji();
C c = new C();
c.gongji();
}
}
类D
package com.examples.basics;
interface wuqi{
public void gongji();
}
class A implements wuqi {
public void gongji() {
System.out.println("Inside A's constructor.");
}
}
class B implements wuqi {
public void gongji() {
System.out.println("Inside B's constructor.");
}
}
class C implements wuqi {
public void gongji() {
System.out.println("Inside C's constructor.");
}
}
class wqkzt {
public void kaihuo(wuqi w) {
w.gongji();
}
}
class D {
public static void main(String args[]){
wqkzt e=new wqkzt();
e.kaihuo(new A());
e.kaihuo(new B());
e.kaihuo(new C());
}
}
其中类D的实现应该和版主的例子是一样的,我只是想知道类D中e.kaihuo(new C())传递参数传递的是对象还是方法。如果是对象的话,那么不就和类E F不就一样了吗。即类D中若多一项e.kaihuo(new D());
那类E
D d = new D();
d.gongji();
类 F
wuqi d = new C();
d.gongji();
(其中类D也实现gongji();)
3个类比起来从哪里可以看出解耦呢
sakulagi 回复于:2005-09-08 08:49:43
传递的当然是对象。
wqkzt类不再关心具体武器的类型。只是对wuqi这个接口进行操作,这就是解耦
sakulagi 回复于:2005-09-08 09:10:18
不过楼主的例子还真是弓虽
用拼音做类名……-_-||
TAxxjszxlkjf 回复于:2005-09-08 19:02:29
e.kaihuo(new A());
e.kaihuo(new B());
e.kaihuo(new C());
wqkzt类这样不也得关心武器的类型吗
(真郁闷就是不明白。)
dennis2 回复于:2005-09-09 00:28:49
> wqkzt类这样不也得关心武器的类型吗
在 wqkzt 这个类的定义中不用(也不可能)关心武器的类型。
sakulagi 回复于:2005-09-09 08:40:05
[quote:06f9522c31="TAxxjszxlkjf"]e.kaihuo(new A());
e.kaihuo(new B());
e.kaihuo(new C());
wqkzt类这样不也得关心武器的类型吗
(真郁闷就是不明白。)[/quote:06f9522c31]你是所有类的程序员,所以你看到了所有的细节。假设你只开发了wqkzt类,其他类你没有source code,你看到了什么?能看到具体的武器类型么?
白色乌鸦 回复于:2005-09-09 11:25:04
@_@! kaihuo??
so cool!!!
TAxxjszxlkjf 回复于:2005-09-09 11:51:47
类D中
wqkzt e=new wqkzt();
e.kaihuo(new A());
类F中
wuqi a = new A();
a.gongji();
二者的实现没什么区别了
版主
在 wqkzt 这个类的定义中不用(也不可能)关心武器的类型只是使用接口的一个优点好处我觉得并不是
接口存在和产生的本质原因
sakulagi 回复于:2005-09-09 13:46:54
解耦就是接口存在的本质原因了。楼主觉得还有什么其他的好处呢?
TAxxjszxlkjf 回复于:2005-09-09 16:57:09
类D中
wqkzt e=new wqkzt();
e.kaihuo(new A());
类F中
wuqi a = new A();
a.gongji();
二者实质上不就一样了吗没什么区别了
sakulagi 回复于:2005-09-09 18:57:54
楼主说的“实质上一样”的意思是什么?
[quote:4b409ea867="TAxxjszxlkjf"]类D中
wqkzt e=new wqkzt();
e.kaihuo(new A());
[/quote:4b409ea867]
D这段代码里有两个对象
[quote:4b409ea867]
类F中
wuqi a = new A();
a.gongji();
二者实质上不就一样了吗没什么区别了[/quote:4b409ea867]F这段代码里只有一个对象。
怎么会实质上一样呢?
TAxxjszxlkjf 回复于:2005-09-10 00:21:17
我的意思 类E F D最终实现的结果是一样的,类E F中是不是就存在着耦合,如果存在,在什么地方,怎么耦合的。类D和类E F比起来是怎么去解耦的。
(我觉着类D中
wqkzt e=new wqkzt();
e.kaihuo(new A());
类F中
wuqi a = new A();
a.gongji();
二者实质上不就一样了吗没什么区别了(从耦合的角度讲))
sakulagi 回复于:2005-09-10 16:15:24
倒,原来你还在看这3个类。D,E,F都不能说明接口的作用,虽然看上去好像是使用了接口。真正体现接口好处的是wqkzt类
我再强调一下:
[quote:e669f41c2f]你是所有类的程序员,所以你看到了所有的细节。假设你只开发了wqkzt类,其他类你没有source code,你看到了什么?能看到具体的武器类型么?
[/quote:e669f41c2f]
TAxxjszxlkjf 回复于:2005-09-11 00:14:23
我问的就是这个意思3个程序最终实现的结果是一样的D.java中使用了wqkzt类,与E F比起来优越在什么地方(程序E F中是不是就存在着耦合,如果存在,在什么地方,怎么耦合的。程序D和程序E F比起来是怎么去解耦的)
sakulagi 回复于:2005-09-11 06:51:57
【1】 只有D使用了wqkzt;E,F没有。
【2】D和E, F相比没有解耦,因为E, F的问题在于E, F这两个类和其他类有强耦合,但是D同样和其他类有耦合。
我说的是如果wqzkt在E, F里有不同的实现:比如下边的实现:
F
[code:1:99afb29279]
class wqzkt {
public void kaihuo(A a) {
a.gongji();
}
public void kaihuo(B b) {
b.gongji();
}
public void kaihuo(C c) {
c.gongji();
}
}
[/code:1:99afb29279]
sakulagi 回复于:2005-09-11 06:57:55
这就是没有使用接口的时候wqzkt的实现。如果使用了接口,就好多了。而且可以支持包括A, B, C在内的所有实现了wuqi接口的类,而不限于这三个类。
所以我说你的那个例子不好。我的例子没有写出没有不用接口的Circus,缺少了对比的效果;你看的那个例子却基本上是误导你用一些错误的类(E, F, D)在对比……
TAxxjszxlkjf 回复于:2005-09-11 13:23:16
既然D和E, F相比没有解耦,也就是说只有在都使用wqzkt类这个前提下才能体现接口的本质,那接口存在还有什么意义。(我觉得只有一个使用接口如程序D,和一个不使用接口如程序E相比较才能反映出接口的本质。)因为E F里没必要实现
class wqzkt {
public void kaihuo(A a) {
a.gongji();
}
public void kaihuo(B b) {
b.gongji();
}
public void kaihuo(C c) {
c.gongji();
}
}
sakulagi 回复于:2005-09-11 20:17:26
[quote:30871f136d="TAxxjszxlkjf"]既然D和E, F相比没有解耦,也就是说只有在都使用wqzkt类这个前提下才能体现接口的本质,那接口存在还有什么意义。(我觉得只有一个使用接口如程序D,和一个不使用接口如程序E相比较才能反映出接口的本质。)因为E F里没必要实现
[/quote:30871f136d]
你说对了,只有使用了接口的程序才能看出接口的好处。但是,不是使用了接口,就一定能享受到接口的好处。比如F,就没有什么好处,因为不是最佳的使用接口的方式。“解耦”意味着“用接口代替类”,而不是接口和类并存,F那个程序充其量只是演示了一下类到接口的类型转换的语法而已,没有任何意义。接口在定义了之后,一定要有一个很好的使用者,比如wqzkt这样的类,才能看出它的用处;对D和F这两个类来说(我不是指D程序和F程序),不是发挥接口优势的类,所以比较D, E, F类是没有意义的。
---------------------------------
至于说E, F里没有必要实现wqzkt类,我只能说这个例子里没有体现wqzkt类存在的必要性。因为这个程序没有需求。只有在复杂的程序里,多人开发的环境中,才能体会到接口的好处。所以在没有需求的情况下,只是单纯的对比D和E是不能看出接口存在的意义的。我是假设wqzkt有存在的意义(抱歉,我无法从这个类的名字看出它的含义),那么最佳的wqzkt类的设计方式是在wqakt类中使用wuqi接口,而不是A, B,C。而你的前提是wqzkt类没有存在的必要,所以只有在有需求的情况下才能看出wuqi接口的作用。
---------------
其实很多软件工程的基本概念开始都不是太容易理解。多写写程序,参与一些实际的项目,然后就会有更清晰的认识。
TAxxjszxlkjf 回复于:2005-09-12 09:38:35
我使用类文件F那种例子就是为了和文件D进行单纯的比较,去比较接口产生的本质。因为我觉得使用接口是java编程的一种方式,讨论它产生的本质不应该局限在都使用类似于wqkzt(武器控制台)类的前提下去讨论(使用接口的好处我也知道,特别是面向不同客户的大型程序中,但那只是它产生后人们发现的好处,和他产生的本质没关系,“多写写程序,参与一些实际的项目,然后就会有更清晰的认识”这样只能更好的理解使用接口的好处,更好的去使用它。我现在只想知道接口为什么产生,是为什么产生的,人们为什么要设计接口。(接口产生的本质)如我举的例子,要实现同一个结果,程序D,程序F两种方式,如果二者在编程的本质上没什么区别(如耦合),虽然外形不一样,那用程序F不就可以了吗,还设计接口干什么。
sakulagi 回复于:2005-09-12 11:00:42
接口设计的目的就是因为有复杂的大型程序,如果是简单的程序,那么面向对象都没有必要,用汇编就够了,何况“接口”。这就是接口产生的目的。
所有的语法现象,包括接口,包括面向对象,都是因为有需求,所以才出现的。所以如果你明白了接口的好处,自然就知道了接口出现的目的。
你举的例子没有需求分析,同时也比较简单,所以看不出接口的作用。也就是说,从你的例子看来,接口这个东西是没有任何用处的。甚至看不出解耦有什么用,因为程序里没有什么太大的耦合度。
Java没有什么特殊的,接口也不是什么神秘的东西,设计java和接口的人就是为了在设计和开发大型程序的时候,有接口这么个东西比没有要方便,而不是为了某个神秘的“本质”设计接口的。我觉得你把接口产生的原因和结果混淆了。不是接口产生了,人们才发现用来设计大型应用程序用接口好;而是因为大型软件的开发要求了“接口”的出现。其实整个软件工程这门科学都是这样。总不能说是“先有了软件工程,大家才发现软件工程可以用来设计和开发软件”吧、
白色乌鸦 回复于:2005-09-12 11:06:02
劳模,当之无愧
sakulagi 回复于:2005-09-12 11:09:56
劳模?俺?
白色乌鸦 回复于:2005-09-12 11:26:28
[quote:0bac37d82b="sakulagi"]劳模?俺?[/quote:0bac37d82b]
恩,pf死你了,呵呵,超有耐心~~~~,很称职的说~~~~~~~~~~~ :)
sakulagi 回复于:2005-09-12 13:45:14
:)
讨论基本的问题是理清思路,学习概念的最好方法
TAxxjszxlkjf 回复于:2005-09-12 23:08:37
程序F E中
..............
class C implements wuqi {
public void gongji() {
System.out.println("Inside C's constructor.");
}
}
class F {
public static void main(String args[]){
wuqi a = new A();
a.gongji();
wuqi b = new B(); //若改为wuqi a = new B();
b.gongji(); //a.gongji();
wuqi c = new C(); //wuqi a = new C();
c.gongji(); //a.gongji();
}
}
............
class E {
public static void main(String args[]){
A a = new A();
a.gongji();
B b = new B(); //若改为B a = new B();
b.gongji(); //a.gongji();
C c = new C(); //C a = new C();
c.gongji(); //a.gongji();
}
}
作以上更改程序在编译时都会报错是行不通的,所以a b c在被赋值后都会同时占用一定的内存空间,而不会立即释放的。
程序D中
...............
class wqkzt {
public void kaihuo(wuqi w) {
w.gongji();
}
}
class D {
public static void main(String args[]){
wqkzt e=new wqkzt();
e.kaihuo(new A());
e.kaihuo(new B());
e.kaihuo(new C());
}
}
其通过接口就可以避免了以上的情况,谁的方法被调用,内存空间就分给谁,如D,当传递的参数是new B()的时候,便使用了开始当传递的参数是new A()时的内存空间。(不知道我理解的对不对,斑竹)