编程到接口是什么意思?

时间:2009-09-11 22:25:28

标签: java interface

我一直听到大多数与节目相关的网站上的声明:

  

编程到界面而不是编程   实施

但我不明白其含义?
例子会有所帮助。

编辑:我收到了很多好的答案,所以你可以用一些代码片段来补充它,以便更好地理解这个主题。谢谢!

17 个答案:

答案 0 :(得分:34)

你可能正在寻找这样的东西:

public static void main(String... args) {
  // do this - declare the variable to be of type Set, which is an interface
  Set buddies = new HashSet();

  // don't do this - you declare the variable to have a fixed type
  HashSet buddies2 = new HashSet();
}

为什么第一种方式做到这一点感觉很好?我们稍后会说你决定使用不同的数据结构,比如LinkedHashSet,以便利用LinkedHashSet的功能。代码必须像这样改变:

public static void main(String... args) {
  // do this - declare the variable to be of type Set, which is an interface
  Set buddies = new LinkedHashSet();  // <- change the constructor call

  // don't do this - you declare the variable to have a fixed type
  // this you have to change both the variable type and the constructor call
  // HashSet buddies2 = new HashSet();  // old version
  LinkedHashSet buddies2 = new LinkedHashSet();
 }

这似乎不是那么糟糕,对吧?但是,如果你以同样的方式写出吸气剂呢?

public HashSet getBuddies() {
  return buddies;
}

这也必须改变!

public LinkedHashSet getBuddies() {
  return buddies;
}

希望您看到,即使使用这样的小程序,您也会对声明变量类型的内容产生深远的影响。如果你只是依赖于一个被声明为接口的变量而不是作为该接口的特定实现(在这种情况下,声明它是一个接口),那么对象来回如此之多,肯定有助于使程序更容易编码和维护设置,而不是LinkedHashSet或其他)。可能就是这样:

public Set getBuddies() {
  return buddies;
}

还有另一个好处,那个(至少对我而言)差异有助于我更好地设计一个程序。但希望我的例子给你一些想法......希望它有所帮助。

答案 1 :(得分:16)

有一天,他的老板指示一名初级程序员编写一份应用程序来分析业务数据,并将其全部压缩在包含指标,图表和所有内容的漂亮报告中。老板给了他一个XML文件,上面写着“这是一些商业数据示例”。

程序员开始编码。几个星期后,他觉得指标和图表和东西足以让老板满意,并且他展示了他的作品。 “这太棒了”老板说,“但它能否显示我们拥有的SQL数据库的业务数据?”。

程序员回到编码。在他的应用程序中,有从XML中读取业务数据的代码。他重写了所有这些片段,用“if”条件包装它们:

if (dataType == "XML")
{
   ... read a piece of XML data ...
}
else
{
   .. query something from the SQL database ...
}

当出现软件的新版本时,老板回答说:“这很好,但它是否也能报告来自这个网络服务的商业数据?”记住所有那些乏味的陈述,他必须重写AGAIN,程序员变得愤怒。 “首先是xml,然后是SQL,现在是Web服务!什么是真正的业务数据来源?”

老板回答:“任何可以提供它的东西”

那时,程序员是enlightened

答案 2 :(得分:7)

我对该陈述的初步阅读与我读过的任何答案都非常不同。我同意所有人说使用接口类型为你的方法参数等非常重要,但这不是这个陈述对我意味着什么。

我的看法是,它告诉你编写的代码只取决于你正在使用的界面(在这种情况下,我使用“interface”来表示类或接口类型的公开方法)在文档中。这与编写代码相反,代码取决于您调用的函数的实现细节。您应该将所有函数调用视为黑盒子(如果两个函数都是同一类的方法,则可以对此进行例外处理,但理想情况是它始终保持不变)。

示例:假设有一个Screen类,其上有Draw(image)Clear()个方法。文档说的是“draw方法在屏幕上绘制指定的图像”和“clear方法清除屏幕”。如果您想按顺序显示图像,正确的方法是重复调用Clear(),然后调用Draw()。这将编码到界面。如果您正在对实现进行编码,那么您可能会执行类似于仅调用Draw()方法的操作,因为您在执行任何绘制之前通过查看内部调用Draw()的{​​{1}}的实现来了解。这很糟糕,因为您现在依赖于通过查看公开的界面无法了解的实现细节。

我期待看到是否有其他人在OP的问题中分享了对该短语的这种解释,或者我是否完全偏离了基础......

答案 3 :(得分:5)

这是一种在模块之间分离职责/依赖性的方法。 通过定义特定的接口(API),可以确保接口任一侧的模块不会互相“打扰”。

例如,假设模块1将负责显示特定用户的银行帐户信息,而module2将从“使用”后端获取银行帐户信息。

通过定义一些类型和函数,以及相关参数,例如定义银行交易的结构,以及一些方法(函数),如GetLastTransactions(AccountNumber,NbTransactionsWanted,ArrayToReturnTheseRec)和GetBalance(AccountNumer),Module1将能够获得所需的信息,而不必担心如何存储或计算此信息或其他任何信息。相反,Module2将通过按照定义的界面提供信息来响应方法调用,但不会担心这些信息的显示,打印或其他任何地方......

当模块被更改时,接口的实现可能会有所不同,但只要接口保持不变,使用API​​的模块最坏的情况可能需要重新编译/重建,但它们不需要无论如何修改了逻辑。

这就是API的想法。

答案 4 :(得分:5)

接口定义了一个提交响应对象的方法

当您编码界面时,您可以更改基础对象,而您的代码将仍然有效(因为您的代码与WHO无关,无法执行此工作或如何工作已经完成)你通过这种方式获得灵活性。

当您对特定实施进行编码时,如果您需要更改基础对象,您的代码将最有可能破解,因为新对象可能无法响应方法。

所以举一个明确的例子:

如果你需要持有许多物品,你可能决定使用Vector

如果你需要访问Vector的第一个对象,你可以写:

 Vector items = new Vector(); 
 // fill it 
 Object first = items.firstElement();

到目前为止一切顺利。

后来你决定,因为“某些”原因你需要改变实现(假设Vector因过度同步而造成瓶颈)

您意识到需要使用ArrayList instad。

好吧,你的代码会破...

ArrayList items = new ArrayList();
// fill it  
Object first = items.firstElement(); // compile time error. 

你做不到。此行以及使用 firstElement()方法的所有行都将中断。

如果你需要特定的行为并且你肯定需要这种方法,它可能没问题(虽然你将无法改变实现)但是如果您需要的是简单地检索第一个element(也就是说,它没有什么特别的,它有firstElement()方法)然后使用接口而不是实现可以让你灵活地改变。

 List items = new Vector();
 // fill it 
 Object first = items.get( 0 ); //

在此表单中,您不是编码get method of Vector,而是get method of List

底层对象如何执行该方法并不重要,只要它响应“获取集合的第0个元素”的契约

这样您以后可以将其更改为任何其他实现:

 List items = new ArrayList(); // Or LinkedList or any other who implements List
 // fill it 
 Object first = items.get( 0 ); // Doesn't break

此示例可能看起来很幼稚,但它是OO技术所依据的基础(即使是那些非静态类型的语言,如Python,Ruby,Smalltalk,Objective-C等)

更复杂的例子是JDBC的工作方式。您可以更改驱动程序,但大多数调用都会以相同的方式工作。例如,您可以使用oracle数据库的标准驱动程序,或者您可以使用Weblogic或Webpshere提供的更复杂的驱动程序。当然,你以前仍然需要测试你的产品并不神奇,但至少你没有像以下那样的东西:

 statement.executeOracle9iSomething();

vs

statement.executeOracle11gSomething();

Java Swing也发生了类似的事情。

补充阅读:

Design Principles from Design Patterns

Effective Java Item: Refer to objects by their interfaces

(购买这本书是你在生活中可以做的最好的事情之一 - 当然还要读 - )

答案 5 :(得分:4)

这个声明的核心是关于依赖性的。如果我将课程Foo编码为实施(Bar而不是IBar),则Foo现在依赖于Bar。但是,如果我将类Foo编码为接口(IBar而不是Bar),则实现可能会有所不同,Foo不再依赖于特定的实现。这种方法提供了一个灵活的,松散耦合的代码库,可以更容易地重用,重构和单元测试。

答案 6 :(得分:3)

拿一个红色的2x4乐高积木并将其固定在一个蓝色的2x4乐高积木上,让一个坐在另一个上面。现在取下蓝色块并用黄色2x4乐高积木替换它。请注意,即使附加块的“实现”发生变化,红色块也不必更改。

现在去获取一些不共享乐高“界面”的其他类型的块。尝试将它附加到红色2x4乐高。为了实现这一点,您需要更换乐高或其他块,可能需要切掉一些塑料或添加新的塑料或胶水。请注意,通过改变“实现”,您将被迫更改它或客户端。

能够让实现变化而不改变客户端或服务器 - 这就是编程到接口的意义。

答案 7 :(得分:3)

接口就像是您与创建接口的人之间的合同,您的代码将执行他们所要求的内容。此外,您希望以这样的方式对事物进行编码,使您的解决方案可以多次解决问题。认为代码重用。当您编写实现时,您正在考虑纯粹是您要解决的问题实例。因此,在这种影响下,您的解决方案将不那么通用,更具针对性。这将使编写一个遵循接口的通用解决方案更具挑战性。

答案 8 :(得分:3)

看,我没有意识到这是针对Java的,我的代码是基于C#的,但我相信它提供了重点。

每辆车都有门。

但不是每一扇门的行为都相同,就像在英国一样,出租车的车门也是倒退的。一个普遍的事实是他们“开放”和“关闭”。

interface IDoor
{
    void Open();
    void Close();
}

class BackwardDoor : IDoor
{
    public void Open()
    {
       // code to make the door open the "wrong way".
    }

    public void Close()
    {
       // code to make the door close properly.
    }
}

class RegularDoor : IDoor
{
    public void Open()
    {
        // code to make the door open the "proper way"
    }

    public void Close()
    {
        // code to make the door close properly.
    }
}

class RedUkTaxiDoor : BackwardDoor
{
    public Color Color
    {
        get
        {
            return Color.Red;
        }
    }
}

如果你是一个车门修理工,你不关心门的外观,或者它是以一种方式还是另一种方式打开。你唯一的要求是门就像一扇门,比如IDoor。

class DoorRepairer
{
    public void Repair(IDoor door)
    {
        door.Open();
        // Do stuff inside the car.
        door.Close();
    }
}

维修人员可以处理RedUkTaxiDoor,RegularDoor和BackwardDoor。以及任何其他类型的门,例如卡车门,豪华轿车门。

DoorRepairer repairer = new DoorRepairer();

repairer.Repair( new RegularDoor() );
repairer.Repair( new BackwardDoor() );
repairer.Repair( new RedUkTaxiDoor() );

将此应用于列表,你有LinkedList,Stack,Queue,普通List,如果你想要你自己的MyList。它们都实现了IList接口,这需要它们实现Add和Remove。因此,如果您的班级在任何给定列表中添加或删除项目......

class ListAdder
{
    public void PopulateWithSomething(IList list)
    {
         list.Add("one");
         list.Add("two");
    }
}

Stack stack = new Stack();
Queue queue = new Queue();

ListAdder la = new ListAdder()
la.PopulateWithSomething(stack);
la.PopulateWithSomething(queue);

答案 9 :(得分:2)

除了其他答案,我还添加了更多:

您编程到界面,因为它更容易处理。该接口封装了底层类的行为。这样,该类就是一个黑盒子。你的整个现实生活是编程到界面。当你使用电视,汽车,立体声音响时,你在它的界面上行动,而不是它的实现细节,你假设如果实施改变(例如柴油发动机或汽油),界面保持不变。通过对接口进行编程,可以在更改,优化或修复无中断详细信息时保留您的行为。这也简化了记录,学习和使用的任务。

此外,对接口进行编程可以让您在编写代码之前描述代码的行为。你希望班级做点什么。您甚至可以在编写实际代码之前测试此内容。当你的界面干净完成,并且你喜欢与它交互时,你可以编写实际的代码。

答案 10 :(得分:2)

Allen Holub在2003年为这个名为Why extends is evil的主题撰写了一篇关于JavaWorld的精彩文章。他对“接口程序”语句的看法,正如你可以从他的标题中收集的那样,你应该乐于实现接口,但很少使用extends关键字来进行子类化。除其他事项外,他指出了所谓的fragile base-class问题。来自维基百科:

  

面向对象编程系统的基本体系结构问题,其中基类(超类)被认为是“脆弱的”,因为在派生类继承时对基类的看似安全的修改可能导致派生类出现故障。程序员无法通过单独检查基类的方法来确定基类更改是否安全。

答案 11 :(得分:2)

“程序到界面”可以更灵活。

例如,我们正在编写一个提供打印服务的类Printer。目前有2个类(CatDog)需要打印。所以我们编写如下代码

class Printer
{
    public void PrintCat(Cat cat)
    {
        ...
    }

    public void PrintDog(Dog dog)
    {
        ...
    }
    ...
}

如果有新课程Bird还需要这个打印服务怎么样?我们必须更改Printer类以添加新方法PrintBird()。在实际情况中,当我们开发Printer类时,我们可能不知道谁将使用它。那么如何写Printer?程序到界面可以提供帮助,见下面的代码

class Printer
{
    public void Print(Printable p)
    {
        Bitmap bitmap = p.GetBitmap();

        // print bitmap ...
    }
}

使用这台新打印机,只要它实现了接口Printable,就可以打印所有内容。这里的方法GetBitmap()只是一个例子。关键是公开接口而不是实现。

希望它有用。

答案 12 :(得分:1)

通过编程到接口,您更有可能应用低耦合/高内聚原理。 通过编程到接口,您可以轻松切换该接口(特定类)的实现。

答案 13 :(得分:1)

这意味着您的变量,属性,参数和返回类型应该具有接口类型而不是具体的实现。

这意味着您使用IEnumerable<T> Foo(IList mylist)代替ArrayList Foo(ArrayList myList)

仅在构造对象时使用实现:

IList list = new ArrayList();

如果你已经完成了这个,你可以稍后更改对象类型,也许你想稍后使用LinkedList而不是ArrayList,这是没有问题的,因为在其他地方你只是将它称为“IList”

答案 14 :(得分:1)

本质上,接口是互操作的一般概念的稍微更具体的表示 - 它们提供了为特定函数“插入”所有可能关注的所有各种选项的规范,以便使用它们的代码获胜不依赖于一个特定的选择。

例如,许多DB库充当接口,因为它们可以与许多不同的实际DB(MSSQL,MySQL,PostgreSQL,SQLite等)一起运行,而不需要使用DB库的代码。

总的来说,它允许您创建更灵活的代码 - 为您的客户提供更多关于如何使用它的选项,并且还可能允许您更轻松地在多个位置重用代码,而不必编写新的专用代码。

答案 15 :(得分:0)

基于接口的编程提供了在运行时没有与特定对象的强耦合。由于Java中的对象变量是多态的,因此对超类的对象引用可以引用其任何子类的对象。使用超类型声明的对象可以使用属于超类型的任何特定实现的对象进行分配。

注意,作为接口可以使用抽象类。

enter image description here

基于实施的编程:

Motorcycle motorcycle = new Motorcycle();
motorcycle.driveMoto();

基于界面的编程:

Vehicle vehicle;
vehicle = new Motorcycle(); // Note that DI -framework can do it for you
vehicle.drive();

答案 16 :(得分:0)

它基本上是你创建这样的方法/接口的地方:create( 'apple' )其中方法create(param)来自抽象类/接口fruit,稍后由具体实现类。这与子类化不同。您正在创建类必须满足的合同。这也减少了耦合,并使每个具体类以不同方式实现它时更加灵活。

客户端代码仍然不知道所使用的特定对象类型,并且仍然不知道实现这些对象的类。客户端代码只知道接口create(param),并使用它来制作水果对象。它就像在说,&#34;我不在乎你是如何得到的,也不是为了我,只是想让你把它给我。&#34;

与此相似的是一组开关按钮。这是一个界面on()off()。您可以在多个设备,电视,收音机,灯光上使用这些按钮。他们都以不同的方式处理它们,但我们并不关心它,我们所关心的只是打开它或关闭它。