「MoreThanJava」Day 7:接口詳解
- 2020 年 8 月 13 日
- 筆記
- MoreThanJava
- 「MoreThanJava」 宣揚的是 「學習,不止 CODE」,本系列 Java 基礎教程是自己在結合各方面的知識之後,對 Java 基礎的一個總回顧,旨在 「幫助新朋友快速高質量的學習」。
- 當然 不論新老朋友 我相信您都可以 從中獲益。如果覺得 「不錯」 的朋友,歡迎 「關注 + 留言 + 分享」,文末有完整的獲取鏈接,您的支持是我前進的最大的動力!
Part 1. 接口概述
Java 是單繼承的。這意味着子類僅從一個父類繼承。通常,這就是你需要的。有時候多繼承會提供方便,但也會造成混亂,例如,當繼承的兩個父類具有不同版本的簽名相同的兩個方法時該調用哪一個呢?
接口為 Java 提供了多繼承的一些優點,而沒有缺點。
接口的概念
在 Java 程序設計語言中,接口不是類,而是對希望符合這個接口的類的一組需求。
我們 之前 接觸的 抽象類,性格偏內向,描述的是一組相對具體的特徵,比如某品牌特定型號的汽車,底盤架構、控制電路、剎車系統等是抽象出來的共同特徵,但根據動感型、舒適型、豪華型的區分,內飾、車頭燈、顯示屏等都可以存放不同版本的具體實現。
而 接口 是開放的,性格偏外向,它就像一份合同,定義了方法名、參數列表、返回值,甚至是拋出異常的類型。誰都可以實現它,但如果想實現它的類就必須遵守這份接口約定的合同。
想一想比較熟悉的 USB 接口:它不僅僅約束了 U 盤 (實現類) 的大小和形狀,同樣也約束了電腦插槽 (使用類)。在編程中,接口類似。
接口的定義
在 Java 中使用 interface
關鍵字來定義接口。接口是頂級的 “類”,雖然關鍵字是 interface
,但編譯之後的位元組碼擴展名還是 .class
。一個典型接口的結構如下:
public interface InterfaceName {
constant definitions
method headers (without implementations).
}
比如,我們在 前面文章 討論「為什麼不推薦使用繼承?」中舉的鳥類的例子,任何能飛的鳥都必須實現如下接口:
public interface Flyable {
void fly();
}
接口中的所有方法都自動是 public
。因此,在接口中聲明方法時,不必提供關鍵字 public
。(在 Java 9 中允許了接口定義聲明為 private
的方法,在這之前都是不允許的..)
想一想接口就像是合同一樣,所以任何不清晰的細節都是不允許的。因此,接口中只允許明確的方法定義和常量出現。(下方的例子中演示了一個不被允許的接口定義 —— 因為
y
變量沒有確定的值)interface ErrorInterfaceDefine { public final int x = 32; public double y; // No variables allowed public double addup(); }
這看起來有點兒像類的定義,但沒有任何對象能夠構建一個接口 (new
一個接口.. 因為接口是絕對抽象的,不允許實現..),但你可以定義一個類實現 (關鍵字 impelents
) 接口,一旦你這麼做了,你就可以構造這個 (實現接口的) 類的對象。
例如麻雀既能飛、也能叫、還能下蛋:(實現多個接口使用 ,
分隔)
public class Sparrow impelents Flayable, Tweetable, EggLayable {//麻雀
//... 省略其他屬性和方法...
@Override
public void fly() { //... }
@Override
public void tweet() { //... }
@Override
public void layEgg() { //... }
}
接口的屬性
❶ 接口不是類,不能使用 new
運算符實例化一個接口,但卻可以用來引用實現了這個接口的類對象:
Comparable x = new Employee(...); // OK provided Emloyee implements Comparable
❷ 與建立類的繼承層次一樣,也可以擴展接口!比如,假設這裡有一個名為 Moveable
的接口:
public interface Moveable {
void move(double x, double y);
}
然後,可以假設一個名為 Powered
的接口擴展了以上的 Moveable
接口:
public interface Powered extends Moveable {
double milesPerGallon();
}
❸ 雖然在接口中不能包含實例字段,但是可以包含常量。比如:
public interface Powered extends Moveable {
double SPEED_LIMIT = 95; // a public static final constant
double milesPerGallon();
}
❹ 另外有一些接口之定義了常量,而沒有定義方法。例如,標準庫中的 SwingConstants
就是這樣一個接口,其中只包含了 NORTH
、SOUTH
和 HORIZONTAL
等常量。任何實現 SwingConstants
接口的類都自動地繼承了這些常量,並可以在方法中引用它們,而不必採用 SwingConstants.NORTH
這樣繁瑣的書寫形式。不過,這樣使用接口更像是退化,所以建議最好不要這樣使用…
➡️ 一個類只能有一個父類,但可以實現很多個接口。這就為定義類的行為提供了極大的靈活性。(我們之前也討論過——在討論繼承的章節——這裡不再贅述)
靜態和私有方法
➡️ 在 Java 8 中,允許在接口中增加靜態方法 (允許不構建對象而直接使用的具體方法)。理論上講,沒有任何理由認為這是不合法的,只是這有違將接口作為抽象規範的初衷。
目前為止,通常的做法都是將靜態方法放在 伴隨類 (可以理解為操作繼承接口的實用工具類) 中。在標準庫中,你可以看到成對出現的接口和實用工具類,如 Collection/ Collections
或 Path/ Paths
。
在 Java 11 中,Path
接口就提供了一個與之工具類 Paths.get()
等價的方法 (該方法用於將一個 URI 或者字符串序列構造成一個文件或目錄的路徑):
public interface Path {
public static Path of(String first, String... more) { ... }
public static Path of(URI uri) { ... }
}
這樣一來,Paths
類就不再是必要的了。類似地,如果實現你自己的接口時,沒有理由再額外提供一個帶有實用方法的工具類。
➡️ 另外,在 Java 9 中,接口中的方法可以是 private
。private
方法可以是靜態方法或實例方法。由於私有方法只能在接口本身的方法中使用,所以它們的用法很有限,只能作為接口中其他方法的輔助方法。
默認方法
在 Java 8 中,允許為接口方法提供一個默認的實現。必須用 default
修飾符標記這樣一個方法,例如 JDK 中的 Iterator
接口:
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() { throw new UnsupportedOperationExceition("remove"); }
}
這將非常有用!如果你要實現一個迭代器,就需要提供 hasNext()
和 next()
方法。這些方法沒有默認實現——它們依賴於你要遍歷訪問的數據結構。不過,如果你的迭代器是 只讀 的,那麼就不用操心實現 remove()
方法。
默認方法也可以調用其他方法,例如,我們可以改造 Collection
接口,定義一個方便的 isEmpty()
方法:
public interface Collection {
int size(); // an abstract method
default boolean isEmpty() { return size() == 0; }
}
這樣,實現 Collection
的程序員就不用再操心實現 isEmpty()
方法了。
(事實上這也是 AbstractCollection
抽象類的定義——所有的集合具體實現幾乎都繼承了 AbstractCollection
抽象類——但為什麼頂層的 Collection
接口不做這樣的修改呢?我起初是懷疑有一些特殊的集合為空的定義有特殊性,但我沒有找到..幾乎所有的集合為空判定都為自身的元素等於 0
。所以答案是什麼呢?是解決默認方法衝突的 “類優先” 原則!👇)
解決默認方法衝突
如果先在一個接口中將一個方法定義為默認方法,然後又在類或另一個接口中定義同樣的方法,會發生什麼?
// 測試接口 1
public interface TestInterface1 {
default void sameMethod() { System.out.println("Invoke TestInterface1 method!"); }
}
// 測試接口 2
public interface TestInterface2 {
default void sameMethod() { System.out.println("Invoke TestInterface2 method!"); }
}
// 繼承兩個接口的測試類
public class TestObject implements TestInterface1, TestInterface2 {
@Override
public void sameMethod() {
// 這裡也可以選擇兩個接口中的一個默認實現
// 如: TestInterface1.super.sameMethod();
System.out.println("Invoke Object method!");
}
}
// 測試類
public class Tester {
public static void main(String[] args) {
TestObject testObject = new TestObject();
testObject.sameMethod();
}
}
測試輸出:
Invoke Object method!
➡️ 對於 Scale
或者 C++
這些語言來說,解決這種具有 二義性 的情況規則會很複雜,Java
的規則則簡單得多:
- 類優先。如果本類中提供了一個具體方法符合簽名,則同名且具有相同參數列表的接口中的默認方法會被忽略;
- 接口衝突。如果一個接口提供了一個默認方法,另一個接口提供了一個同名且參數列表相同的方法 (順序和類型都相同) ,則必須覆蓋這個方法來解決衝突 (就是👆代碼的情況,不覆蓋編譯器不會編譯..);
Java 設計者更強調一致性,讓程序員自己來解決這樣的二義性似乎也顯得很合理。如果至少有一個接口提供了一個實現,編譯器就會報告錯誤,程序員就必須解決這個二義性。(如果兩個接口都沒有為共享方法提供默認實現,則不存在衝突,要麼實現,要麼不實現..)
➡️ 我們只討論了兩個接口的命名衝突。現在來考慮另一種情況,一個類繼承自一個類,同時實現了一個接口,從父類繼承的方法和接口擁有同樣的方法簽名,又將怎麼辦呢?
// 測試接口
public interface TestInterface {
default void sameMethod() { System.out.println("Invoke TestInterface Method!"); }
}
// 父類
public class Father {
void sameMethod() { System.out.println("Invoke Father Method!"); }
}
// 子類
public class Son extends Father implements TestInterface {
@Override
public void sameMethod() {
System.out.println("Invoke Son Method!");
}
}
// 測試類
public class Tester {
public static void main(String[] args) { new Son().sameMethod(); }
}
程序輸出:
Invoke Son Method!
還記得我們說過的方法調用的過程嗎 (先找本類的方法找不到再從父類找)?加上這裡提到的 “類優先” 原則 (本類中有方法則直接調用),這很容易理解!
千萬不要讓一個默認方法重新定義
Object
類中的某個方法。例如,不能為toString()
或equals()
定義默認方法,儘管對於 List 之類的接口這可能很有吸引力,但由於 類優先原則,這樣的方法絕對無法超越Object.toString()
或者Object.equals()
。(這裡就對應上方思考為什麼不在
Collection
中定義默認的isEmpty()
方法的答案)
Part 2. 接口與工廠模式
這一部分節選自 極客時間 | 設計模式之美://time.geekbang.org/column/article/197254
原作者:王爭
接口是實現多重繼承的途徑,而生成遵循某個接口的對象的典型方式就是 工廠方法設計模式。這與直接調用構造器構造對象不同,我們在工廠對象上調用的是創建方法,而該工廠對象將生成接口的某個實現對象。
理論上,通過這種方式,我們的代碼將完全與接口的實現分離,這就使得我們可以透明地將某個實現替換為另一個實現。下面我們來舉例演示一下。
簡單工廠模式
假設我們現在需要根據文件的後綴名 (json、xml、yaml) 來選擇不同的解析器 (JsonRuleConfigParser、XmlRuleConfigParser),將存儲在文件中的配置解析成內存對象 RuleConfig:
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParser parser = null;
if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new JsonRuleConfigParser();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new XmlRuleConfigParser();
} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new YamlRuleConfigParser();
} else {
throw new InvalidRuleConfigException(
"Rule config file format is not supported: " + ruleConfigFilePath);
}
String configText = "";
//從ruleConfigFilePath文件中讀取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名獲取擴展名,比如rule.json,返回json
return "json";
}
}
➡️ 為了讓代碼邏輯更加清晰,可讀性更好,我們要善於 將功能獨立的代碼塊封裝成函數。按照這個設計思路,我們可以將代碼中涉及 parser
創建的部分邏輯剝離出來,抽象成 createParser()
函數。重構之後的代碼如下所示:
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParser parser = createParser(ruleConfigFileExtension);
if (parser == null) {
throw new InvalidRuleConfigException(
"Rule config file format is not supported: " + ruleConfigFilePath);
}
String configText = "";
//從ruleConfigFilePath文件中讀取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名獲取擴展名,比如rule.json,返回json
return "json";
}
private IRuleConfigParser createParser(String configFormat) {
IRuleConfigParser parser = null;
if ("json".equalsIgnoreCase(configFormat)) {
parser = new JsonRuleConfigParser();
} else if ("xml".equalsIgnoreCase(configFormat)) {
parser = new XmlRuleConfigParser();
} else if ("yaml".equalsIgnoreCase(configFormat)) {
parser = new YamlRuleConfigParser();
}
return parser;
}
}
➡️ 為了讓類的職責更加單一、代碼更加清晰,我們還可以進一步將 createParser()
函數剝離到一個單獨的類中,讓這個類只負責對象的創建。而這個類就是我們現在要將的 簡單工廠 模式類。具體的代碼如下所示:
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
if (parser == null) {
throw new InvalidRuleConfigException(
"Rule config file format is not supported: " + ruleConfigFilePath);
}
String configText = "";
//從ruleConfigFilePath文件中讀取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名獲取擴展名,比如rule.json,返回json
return "json";
}
}
public class RuleConfigParserFactory {
public static IRuleConfigParser createParser(String configFormat) {
IRuleConfigParser parser = null;
if ("json".equalsIgnoreCase(configFormat)) {
parser = new JsonRuleConfigParser();
} else if ("xml".equalsIgnoreCase(configFormat)) {
parser = new XmlRuleConfigParser();
} else if ("yaml".equalsIgnoreCase(configFormat)) {
parser = new YamlRuleConfigParser();
}
return parser;
}
}
(這樣的 Factory 代碼暫稱為第一種實現)
在類的命名中體現設計模式是非常好的方式 (例如這裡的 RuleConfigParserFactory
)。大部分工廠類都是以 「Factory」
這個單詞結尾的,但也不是必須的,比如 Java 中的 DateFormat
、Calender
。
除此之外,工廠類中創建對象的方法一般都是 create
開頭,比如代碼中的 createParser()
,但有的也命名為 getInstance()
、createInstance()
、newInstance()
,有的甚至命名為 valueOf()
(比如 Java String 類的 valueOf()
函數) 等等,這個我們根據具體的場景和習慣來命名就好。
➡️ 在上面的代碼實現中,我們每次調用 RuleConfigParserFactory 的 createParser()
的時候,都要創建一個新的 parser
。實際上,如果 parser
可以復用,為了節省內存和對象創建的時間,我們可以將 parser
事先創建好緩存起來。當調用 createParser()
函數的時候,我們從緩存中取出 parser
對象直接使用:
public class RuleConfigParserFactory {
private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();
static {
cachedParsers.put("json", new JsonRuleConfigParser());
cachedParsers.put("xml", new XmlRuleConfigParser());
cachedParsers.put("yaml", new YamlRuleConfigParser());
}
public static IRuleConfigParser createParser(String configFormat) {
if (configFormat == null || configFormat.isEmpty()) {
return null;//返回null還是IllegalArgumentException全憑你自己說了算
}
IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
return parser;
}
}
(這樣的 Factory 代碼暫稱為第二種實現)
這有點類似於單例模式和簡單工廠模式的結合。
但上面兩種實現的簡單工廠,都有違背 開閉原則 (對擴展開放,對修改關閉)。想像一下現在我們如果要新增一種 parser
,那麼勢必會修改 RuleCOnfigParserFactory
裏面的代碼!但好在就日常的使用來說,如果不是需要頻繁地添加新的 parser
,只是偶爾修改一下 RuleConfigParserFactory 代碼,稍微不符合開閉原則,也是完全可以接受的。
工廠方法
回看👆我們上方的第一種實現,如果可能的話,我們的 if-else
代碼會隨着文件種類的增加列得越來越長,最終不僅可讀性很差,也變得更加難以維護 (複雜度增加),而且也不怎麼優雅。
如果我們非得去掉 if-else
分支邏輯的話,應該怎麼辦呢?比較經典處理方法就是利用多態。按照多態的實現思路,對上面的代碼進行重構。重構之後的代碼如下所示:
public interface IRuleConfigParserFactory {
IRuleConfigParser createParser();
}
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
@Override
public IRuleConfigParser createParser() {
return new JsonRuleConfigParser();
}
}
public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
@Override
public IRuleConfigParser createParser() {
return new XmlRuleConfigParser();
}
}
public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
@Override
public IRuleConfigParser createParser() {
return new YamlRuleConfigParser();
}
}
實際上,這就是工廠方法模式的典型代碼實現。這樣當我們新增一種 parser 的時候,只需要新增一個實現了 IRuleConfigParserFactory 接口的 Factory 類即可。所以,工廠方法模式比起簡單工廠模式更加符合開閉原則。
從上面的工廠方法的實現來看,一切都很完美,但是實際上存在挺大的問題。問題存在於這些工廠類的使用上。接下來,我們看一下,如何用這些工廠類來實現 RuleConfigSource 的 load()
函數。具體的代碼如下所示:
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParserFactory parserFactory = null;
if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new JsonRuleConfigParserFactory();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new XmlRuleConfigParserFactory();
} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new YamlRuleConfigParserFactory();
} else {
throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
}
IRuleConfigParser parser = parserFactory.createParser();
String configText = "";
//從ruleConfigFilePath文件中讀取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名獲取擴展名,比如rule.json,返回json
return "json";
}
}
從上面的代碼實現來看,工廠類對象的創建邏輯又耦合進了 load()
函數中,跟我們最初的代碼版本非常相似,引入工廠方法非但沒有解決問題,反倒讓設計變得更加複雜了。那怎麼來解決這個問題呢?
我們可以為工廠類再創建一個簡單工廠,也就是 工廠的工廠,用來創建工廠類對象。這段話聽起來有點繞,我把代碼實現出來了,你一看就能明白了。其中,RuleConfigParserFactoryMap 類是創建工廠對象的工廠類,getParserFactory()
返回的是緩存好的單例工廠對象。
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);
if (parserFactory == null) {
throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
}
IRuleConfigParser parser = parserFactory.createParser();
String configText = "";
//從ruleConfigFilePath文件中讀取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名獲取擴展名,比如rule.json,返回json
return "json";
}
}
//因為工廠類只包含方法,不包含成員變量,完全可以復用,
//不需要每次都創建新的工廠類對象,所以,簡單工廠模式的第二種實現思路更加合適。
public class RuleConfigParserFactoryMap { //工廠的工廠
private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();
static {
cachedFactories.put("json", new JsonRuleConfigParserFactory());
cachedFactories.put("xml", new XmlRuleConfigParserFactory());
cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
}
public static IRuleConfigParserFactory getParserFactory(String type) {
if (type == null || type.isEmpty()) {
return null;
}
IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());
return parserFactory;
}
}
當我們需要添加新的規則配置解析器的時候,我們只需要創建新的 parser
類和 parser factory
類,並且在 RuleConfigParserFactoryMap 類中,將新的 parser factory
對象添加到 cachedFactories
中即可。代碼的改動非常少,基本上符合開閉原則。
實際上,對於規則配置文件解析這個應用場景來說,工廠模式需要額外創建諸多 Factory 類,也會增加代碼的複雜性,而且,每個 Factory 類只是做簡單的 new
操作,功能非常單薄 (只有一行代碼),也沒必要設計成獨立的類,所以,在這個應用場景下,簡單工廠模式簡單好用,比工廠方法模式更加合適。
什麼時候該用工廠方法模式呢?
我們前面提到,之所以將某個代碼塊剝離出來,獨立為函數或者類,原因是這個代碼塊的邏輯過於複雜,剝離之後能讓代碼更加清晰,更加可讀、可維護。但是,如果代碼塊本身並不複雜,就幾行代碼而已,我們完全沒必要將它拆分成單獨的函數或者類。
所以讓我們有足夠理由使用工廠方法模式的情況大概有以下兩點:
- 當對象的創建邏輯比較複雜,不只是簡單的
new
一下就可以,而是要組合其他類對象,做各種初始化操作的時候; - 避免煩人的
if-else
分支邏輯時;
抽象工廠(Abstract Factory)
在簡單工廠和工廠方法中,類只有一種分類方式。比如,在規則配置解析那個例子中,解析器類只會根據配置文件格式 (Json、Xml、Yaml……) 來分類。但是,如果類有兩種分類方式,比如,我們既可以按照配置文件格式來分類,也可以按照解析的對象 (Rule 規則配置還是 System 系統配置) 來分類,那就會對應下面這 6
個 parser
類。
針對規則配置的解析器:基於接口IRuleConfigParser
JsonRuleConfigParser
XmlRuleConfigParser
YamlRuleConfigParser
針對系統配置的解析器:基於接口ISystemConfigParser
JsonSystemConfigParser
XmlSystemConfigParser
YamlSystemConfigParser
針對這種特殊的場景,如果還是繼續用工廠方法來實現的話,我們要針對每個 parser 都編寫一個工廠類,也就是要編寫 6
個工廠類。如果我們未來還需要增加針對業務配置的解析器 (比如 IBizConfigParser),那就要再對應地增加 4
個工廠類。而我們知道,過多的類也會讓系統難維護。這個問題該怎麼解決呢?
抽象工廠就是針對這種非常特殊的場景而誕生的。我們可以讓一個工廠負責創建多個不同類型的對象 (IRuleConfigParser、ISystemConfigParser 等),而不是只創建一種 parser
對象。這樣就可以有效地減少工廠類的個數。具體的代碼實現如下所示:
public interface IConfigParserFactory {
IRuleConfigParser createRuleParser();
ISystemConfigParser createSystemParser();
//此處可以擴展新的parser類型,比如IBizConfigParser
}
public class JsonConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new JsonRuleConfigParser();
}
@Override
public ISystemConfigParser createSystemParser() {
return new JsonSystemConfigParser();
}
}
public class XmlConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new XmlRuleConfigParser();
}
@Override
public ISystemConfigParser createSystemParser() {
return new XmlSystemConfigParser();
}
}
// 省略YamlConfigParserFactory代碼
重點回顧
- 接口的概念 / 接口的定義 / 接口的實現 / 接口的屬性;
- 接口的靜態和私有方法 / 如何解決默認方法的衝突;
- 接口和工廠模式;
練習
練習 1:實現一個圖形繪製工具
創建一個可以繪製不同形狀的繪圖工具,可以繪製圓形、矩形、三角形,每個圖形都會有一個
draw()
方法用於繪圖,而繪圖工具也有一個draw()
方法,根據傳入類型的不同調用不同的方法。
創建 IShape 接口:
public interface IShape {
void draw();
}
繼承 IShape 接口創建圓形、矩形、三角形:
// 圓形
public class Circle implements IShape {
@Override
public void draw() { System.out.println("Draw Circle..."); }
}
// 矩形
public class Rectangle implements IShape {
@Override
public void draw() { System.out.println("Draw Rectangle..."); }
}
// 三角形
public class Triangle implements IShape {
@Override
public void draw() { System.out.println("Draw Triangle..."); }
}
圖形繪製工具:
public class Paint {
public static void draw(IShape shape) {
shape.draw();
}
}
測試類:
public class Tester {
public static void main(String[] args) {
Paint.draw(new Circle());
Paint.draw(new Rectangle());
Paint.draw(new Triangle());
}
}
程序輸出:
Draw Circle...
Draw Rectangle...
Draw Triangle...
(ps:說實話這一篇文章雖然寫了兩天.. 但感覺總體質量挺差的.. 原因有許多,一來是發現存在很多知識點交叉的情況——也就是說知識是互相聯繫的,想要說清楚不容易——而且常常組織起來非常龐大。二來是發現光說清楚一個知識點也挺不容易的..所以在考慮新的組織形式.. 最近有接觸到一些雙向鏈接的工具.. 探索探索..)
參考資料
- 《Java 核心技術 卷 I》
- 《Java 編程思想》
- Introduction to Computer Science using Java – //programmedlessons.org/Java9/index.html
- 極客時間 | 設計模式之美 – //time.geekbang.org/column/article/177110
- 本文已收錄至我的 Github 程序員成長系列 【More Than Java】,學習,不止 Code,歡迎 star://github.com/wmyskxz/MoreThanJava
- 個人公眾號 :wmyskxz,個人獨立域名博客:wmyskxz.com,堅持原創輸出,下方掃碼關注,2020,與您共同成長!
非常感謝各位人才能 看到這裡,如果覺得本篇文章寫得不錯,覺得 「我沒有三顆心臟」有點東西 的話,求點贊,求關注,求分享,求留言!
創作不易,各位的支持和認可,就是我創作的最大動力,我們下篇文章見!
(另外這些基礎的知識體系我打算自己偷偷慢慢在博客搭建啦.. 等有確實的成果之後再分享吧.. 公眾號還是希望分享更多能對小夥伴們有用的實際的東西.. Respect~)