国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

抽象類和接口-實戰練習

2019-11-18 15:05:02
字體:
來源:轉載
供稿:網友

  在java中,什么時候該用抽象類,什么時候該用接口?下面的文章將通過實例,而不是從理論的角度給你一個清楚的回答,讓你有可能獲得醍醐灌頂般的徹悟。以下是原文翻譯

----------------------------------------------------

其中經常提到的一個要求是,希望能夠提供一個完整的例子,來說明到底該如何使用接口(interface)和抽象類(abstract class)。看來,我上次的回答過于理論化了。所以,在本次Java Q&A專題中,我將通過一個使用了接口和抽象類的程序實例,將這一討論繼續深化。

進行網絡通信編程時,大家會發現,通信往往是通過 "成對的鍵和值" (key/value pair,以下稱為 "鍵/值對")的傳輸來完成的。HTTP POST和HTTP GET都采用了 "鍵/值對" 通信。和WebMethod這樣的EAI服務器通信,"鍵/值對" 通信也是一種選擇的機制。甚至在使用Java特性時也可以看到 "鍵/值對"。"鍵/值對" 無處不在。

"鍵/值對" 通信之所以常見,在于它可以通過簡單的機制賦予數據以含義。雖然簡單,但對于每種協議來說,將 "鍵/值對" 數據放到線路上的方式卻各不相同。假如想和Web服務器通信,你會使用URLConnection來和服務器進行HTTP連接。其它類型的通信則需要你使用其它某種方式。本專題中,我將演示如何使用接口和抽象類來生成一個程序框架,使得這個程序可以和任何支持 "鍵/值對" 消息的服務器通信。

對"鍵/值對" 通信可以提取兩種抽象。第一,傳送給接收者的 "鍵/值對" 數據構成一條Message。第二,消息是通過某種協議傳送給服務器的。可以將這種協議抽象為MessageBus。所以,假如要和Web服務器通信,可以通過HTTP MessageBus傳送消息。

被傳送的消息以及傳送消息的機制會經常變化,至少不同程序之間是這樣。當你確信某個東西會經常發生變化時,它就是接口的最好選擇。

下面一一分析我們的消息發送程序所需要的各個接口。

MessageBus
從下面的代碼可以看到,MessageBus知道它能夠將一個二維數組類型的 "鍵/值對" 傳送給某個接收者。但要注重,這個接口并沒有說如何傳送或者傳送給誰。相反,這些細節留給了實現這個接口的類:

public interface MessageBus {
public String sendMessage( String [][] values ) throws BusException;
}

這個接口使用起來功能十分強大。你可以用URLConnections來實現它,以進行HTTP通信;也可以通過socket,用自定義協議來實現它;你甚至可以只是將數據記錄到普通文件或數據庫中。

總之,這個接口答應你建立多種不同的實現。更進一步來說,采用接口而不是某個特定的實現來進行編程,你就能夠盡享多態所帶來的好處,可以在你的程序中隨意更換各種不同的實現。

舉例來說,當想調試程序時,你可以將一個 "HTTP MessageBus實現" 替換成一個 "通信記錄實現"。這種方法使得你可以輕松地改變程序的工作方式,而無需對原始程序做大量的修改。任何時候需要支持一種新的通信方式,只需簡單地建立一個新的MessgaeBus實現就可以了。

Message
下面的示例代碼中,Message只知道可以通過MessageBus將自己傳送出去。它的具體實現才會去考慮如何得到Message的值。

注重:你不可能找到一個用于獲得Message值的函數。相反,我們讓Message完全包裝它的數據,并且相信它可以將自己正確地放到MessageBus上:

public interface Message {
public String send( MessageBus mb ) throws BusException;
}

MessageBus使得你可以在支持MessageBus的程序中增加新的通信機制;與此類似,Message使得你任何時候可以在程序中增加新的Message類型。

HttpMessageBus
看下面一個具體的MessageBus實現,它通過HTTP來POST消息:

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
* HttpMessageBus是一個MessageBus實現,
* 它采用HTTP POST來發送消息。
* @author Tony Sintes JavaWorld Q&A
*/
public class HttpMessageBus implements MessageBus {

PRivate String _url;

private final static String _KEY_VALUE_DELIM = "=";
private final static String _NEW_KEY_VALUE = "&";
private final static String _ENCODING = "application/x-www-form-urlencoded";
private final static String _TYPE = "Content-Type";
private final static int _KEY = 0;
private final static int _VALUE = 1;

public HttpMessageBus( String url )
{
_url = url;
}

public String sendMessage( String[][] values ) throws BusException
{
String post = _formulatePOST( values );
try
{
return _sendPOST( post );
}
catch( MalformedURLException exception )
{
throw new HttpBusException( exception.getMessage() );
}
catch( IOException exception )
{
throw new HttpBusException( exception.getMessage() );
}
}

private String _formulatePOST( String [][] values ) {
if( ( values == null ) ( values.length == 0 ) )
{
return "";
}

StringBuffer sb = new StringBuffer();
String new_pair = "";

for( int i = 0; i < values.length; i ++ )
{
sb.append( new_pair );
sb.append( URLEncoder.encode( values[i][_KEY] ) );
sb.append( _KEY_VALUE_DELIM );
sb.append( URLEncoder.encode( values[i][_VALUE] ) );
new_pair = _NEW_KEY_VALUE;
}

String post = sb.toString();
return post;
}

private String _sendPOST( String post ) throws MalformedURLException, IOException
{
URLConnection conn = _setUpConnection();
_write( post, conn );
return _read( conn );
}

private URLConnection _setUpConnection() throws MalformedURLException, IOException
{
URL url = new URL( _url );
URLConnection conn = url.openConnection();

conn.setDoInput ( true );
conn.setDoOutput ( true );
conn.setUseCaches ( false );
conn.setRequestProperty( _TYPE, _ENCODING );

return conn;
}

private void _write( String post, URLConnection conn ) throws IOException
{
DataOutputStream output = new DataOutputStream ( conn.getOutputStream() );
output.writeBytes( post );
output.flush ();
output.close ();
}

private String _read( URLConnection conn ) throws IOException
{
BufferedReader input =
new BufferedReader( new InputStreamReader( conn.getInputStream() ) );
String temp_string;
StringBuffer sb = new StringBuffer();
while ( null != ( ( temp_string = input.readLine() ) ) )
{
sb.append( temp_string );
}
input.close ();

return sb.toString();
}

}

大家可以自己研究一下這段代碼。關于POST,可以在Java World網站的Java Tip欄目中找到很多介紹。在此我要強調的是,在一個簡簡單單的MessageBus接口背后,隱藏了大量的細節。HttpMessageBus在構造時只是簡單地取了一個URL;傳給sendMessage()的所有值都發送到那個URL。除此之外的任何細節都隱藏在接口之后。

AbstractMessage
寫過幾個Message實現之后就會發現,send()可以分解為兩個基本步驟:

1. 將Message的內部數據轉換成二維數組。

2. 在MessageBus上發送數組值。這是通過調用MessageBus的sendMessage()并傳給它二維數組參數來實現的。

每次寫一個新的Message實現,都要寫類似的send()。

若能合理地使用抽象類,你將能夠很輕松地寫出新的Message實現并消除冗余代碼。看看下面這個AbstractMessage的定義:

public abstract class AbstractMessage implements Message
{

public String send(MessageBus mb) throws BusException
{
String [][] values = values();
String response = mb.sendMessage( values );
return response;
}

protected abstract String [][] values();

}

從上面可以看到,AbstractMessage為send()提供了一個缺省實現。這一缺省實現帶來三大好處:

? 你無需一遍又一遍地寫相同的代碼。相反,現在有了一個缺省實現,它可以完成前面所定義的那兩步基本操作。

? 現在,你只用提供一個values()的實現就可以寫出一個新的Message。這種方式下,子類只需要知道如何將自身表示為 "鍵/值對" 數組就可以了。子類不必關心如何在線路上傳送自己。傳送的動作對任何Message來說都是相同的。

繼續(inheritance)的一個重要用途在于,它可以用于 "根據差異來編程" (programming by difference)的場合。根據差異去編程,也就確定了子類與它的父類如何不同。例如Message,它和父類的差異僅僅在于所包含的數據不同,在線路上傳送的方式則是一樣的。采用抽象類,使得我們可以干凈清楚地使用繼續。

? AbstractMessage實際定義了一個規范,通過子類來定義新的Message時都要遵守這一規范。這樣一來,程序員在建立子類時就可以清楚地知道需要重新實現哪些函數。上面的例子雖然很簡單,但采用這種編程方法使得在派生復雜的子類時,事情會變得更簡單。

隨著一個類越變越大,以上三點也會更趨明顯。

Message示例
假設有一個網站提供股票行情服務。若想查找一支股票信息,就要對某一URL發送POST命令。除了URL之外,還要附上股票代號的名稱。

股票代號的 "鍵/值對" 看起來象這樣:

ticker=bvsn

此處的ticker是 "鍵",bvsn是 "值"。鍵告訴接收者,值bvsn是一個股票代號。

假如在Yahoo查看股票行情,你要發送這樣的URL:

http://finance.yahoo.com/q

以及兩對鍵/值:

? s - 股票代號
? d - 查看級別(基本信息,具體信息等)

所以,想要查看bvsn,需要POST下面的消息:

http://finance.yahoo.com/q?s=bvsn&d=v1

類似地,假如是Quicken的股票行情系統,你得發送這樣的URL:

http://www.quicken.com/investments/quotes/

以及一對鍵/值:

symbol - 股票代號

所以,在Quicken查看bvsn,需要POST下面的消息:

http://www.quicken.com/investments/quotes/?symbol=bvsn

下面的代碼是針對這兩種Message的實現:

public class YahooStockQuote extends AbstractMessage {

private String _ticker;

private final static String _TICKER_KEY = "s";
private final static String _D_VALUE = "v1";
private final static String _D_KEY = "d";

public YahooStockQuote( String ticker )
{
_ticker = ticker;
}

public void setTicker( String ticker )
{
_ticker = ticker;
}

protected String [][] values()
{
String [][] values = {
{ _TICKER_KEY, _ticker },
{ _D_KEY, _D_VALUE }
};
return values;
}
}

以及:

public class QuickenStockQuote extends AbstractMessage
{

private String _ticker;

private final static String _TICKER_KEY = "symbol";

public QuickenStockQuote( String ticker )
{
_ticker = ticker;
}

public void setTicker( String ticker )
{
_ticker = ticker;
}

protected String [][] values()
{
String [][] values = {
{ _TICKER_KEY, _ticker }
};
return values;
}
}

這些Message只知道如何處理它們的數據以及構造二維 "鍵/值對" 數組。

程序示例
看看下面的股票信息示例程序:

public class QuoteGetter
{

public final static void main( String [] args )
{
if( args.length != 2 )
{
System.out.println( "Incorrect Useage:" );
System.out.println( "java QuoteGetter <quicken or yahoo> <TICKER>" );
return;
}

String service = args[0];
String ticker = args[1];

Message message = null;
MessageBus bus = null;
if( service.equalsIgnoreCase( "quicken" ) )
{
bus =
new HttpMessageBus( "http://www.quicken.com/investments/quotes/" );
message = new YahooStockQuote( ticker );
}
else // default to yahoo
{
bus =
new HttpMessageBus( "http://finance.yahoo.com/q" );
message = new QuickenStockQuote( ticker );
}

try
{
String response = sendMessage( message, bus );
System.out.println( response );
}
catch( Exception ignore )
{
System.out.println( "Lookup Failed for: " + ticker );
ignore.printStackTrace();
}
}

private static String sendMessage( Message message, MessageBus bus )
throws BusException
{
return message.send( bus );
}

}

main首先創建合適的Message和MessageBus,然后調用sendMessage()發送Message。sendMessage()是一個針對接口來進行編程的例子,而不是針對實現。假如sendMessage()只是專門支持YahooStockQuote或HttpMessageBus,你會說它是針對實現來編程的。

然而,上面的sendMessage()是針對接口來編程的。所以你可以將這個接口的任何實現作為參數傳遞給這個函數,使得函數更具一般性。

總結
我在日常工作中已經成功地使用了這一程序框架。通過接口,我可以將通信機制從HTTP POST無縫地轉換為一個自定義的協議。我還可以使用Message接口在程序中輕松地增加新的消息。另外,只要寫一次Message抽象基類,我可以更輕易地寫出新的函數并在其它程序中復用它們。

希望這個詳盡的例子有助于深化上一次關于抽象類和接口的探討。另外,讀者可以去下載本專題所附帶的源代碼,它包括整個消息發送程序的所有代碼以及本專題未曾提到的其它一些類的代碼。

----------------------------------------------------
相關資源

? 下載本篇文章的源代碼:
http://www.javaworld.com/javaworld/javaqa/2001-08/interface/03-qa-0831-interface.zip

? 和本文相關的另一篇文章 "Abstract Classes Vs. Interfaces" Tony Sintes (JavaWorld, April 2001):
http://www.javaworld.com/javaworld/javaqa/2001-04/03-qa-0420-abstract.Html

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 吴江市| 西和县| 华坪县| 雅安市| 宜宾市| 正阳县| 河西区| 察隅县| 天全县| 桂林市| 裕民县| 九台市| 乐安县| 上蔡县| 洞口县| 泰宁县| 湘乡市| 南京市| 保德县| 古浪县| 连州市| 黄陵县| 福海县| 延长县| 勐海县| 商河县| 连江县| 锡林郭勒盟| 二手房| 翁源县| 诸暨市| 成都市| 沙河市| 瓦房店市| 霍林郭勒市| 海伦市| 长治县| 安平县| 广德县| 濮阳县| 和硕县|