1、Java是一個笨重的垃圾語言嗎?
一個語言很笨重,不是研發者要故意讓這個語言變得很笨重,現在語言這麼多,如果沒有它自己的優勢,是很難存活的。
在只瞭解了一些JS、Python之類的動態語言之後,就盲目的下結論是不正確的。
Java 笨重是有笨重的原因的,這是因爲Java通常是用來做企業級的項目,或者說是複雜的大項目。
2、爲什麼在企業中不會優先選擇使用動態語言去做大項目
不是說動態語言不能寫大項目,但是動態語言在寫大項目的時候,會有很多問題,並且寫出來的代碼很難維護。
但是Java或者C#這類編譯形語言編寫項目,寫的時候比較麻煩(比如Java強制使用類的思想去表達),主要是爲了維護方便
多數情況下,開發者考慮的都是如何快速的將代碼寫完,這個想法其實是不正確的,因爲對於軟件工程來講,寫出項目來並不是軟件工程要解決的主要問題
軟件工程主要解決的問題是迭代、維護。
3、開發過程中的方法論
開發中的軟件工程、軟件方法論,其實主要解決的就是項目的維護和迭代,而不是簡單的把一個項目開發出來就完事了
如果編程只是爲了把項目寫出來,那麼項目一點都不難
關鍵是對於自己來講,不追求一些軟件工程上面的方法論,軟件的編程水平是沒辦法提升的。因爲你永遠都是在追求把代碼寫出來而已。
4、一個程序員好,到底好在什麼地方
一個好的程序員,除了綜合素質(每一個程序員都要去追求的或者說是特質),追求可維護的代碼
如果不是,那麼學習Java只是在浪費時間而已
5、糟糕的代碼
糟糕的代碼並非指的是代碼寫的很醜,有些動態語言寫的代碼也很漂亮很美
這裏的糟糕指的是,不可維護。也就是你自己都很難去更改自己寫的代碼
並不是說Python、JS就無法寫出可維護的代碼,只不過要真正寫的好的可維護的代碼,很難
雖然說Java、C#笨重,但也正是因爲它們的強制性,因此不需要太好的基本功也不需要太過於瞭解軟件上的方法論,只需要按照語言的特質,就能寫出很好的可維護的代碼
很多觀點是,寫出簡短的代碼就是好的代碼,但真的是這樣嗎?這個不見得
什麼是好的代碼?通俗來講就是,不囉嗦,自描述性的代碼(看這個代碼不需要過多的註釋就能理解清楚代碼的意思)
還有一個最重要的,也是各種各樣軟件工程裏探討的:可維護性
所有軟件的複雜性,其實都是爲了寫出可維護的代碼
6、SpringBooot 爲什麼會有這麼多複雜的概念
SpringBoot 中應用了大量的設計模式,本身SpringBoot中融合許多的框架,比如SpringFrameWork,SpringFrameWok中又有IOC和AOP,還有各種各樣的MVC、JMS等模塊
面試其實問的並不深,比如 Spring 容器中bean的加載過程,但瞭解這個過程需要看源碼嗎?顯然不需要
SpringBoot把一個對象加入IOC容器之前,有一個重要的點就是,BeanDefination也即Bean的定義
7、怎麼樣才能叫好維護或者說怎麼樣可以稱之爲可維護的代碼?
軟件工程裏面必要重要的幾個原則:
- 開閉原則(最重要的,可維護代碼的基礎)
- 里氏替換原則
- 迪米特法則
多數原則其實都是爲了實現開閉原則,比如IOC、DI
OCP:open、close、principle
軟件、函數、類這幾者都應該對擴展是開放的,而對修改是封閉的。
eg:避免修改原有代碼帶來bug的最好方法就是新增一個類/業務模塊,使用新增來代替原來的類。當然不是時候新增的模塊不會有bug,只是相比修改原來複雜的代碼出現bug的概率更小一些。
8、如果要實現開閉原則,有一個最基準的原則
必須面向抽象編程,只有做到這一點,才能逐步地實現開閉原則。
很多時候,Java之所以複雜,就是它本身就是爲面向抽象而設計的語言,所以在Java中有了interface、Abstract等概念
9、再具體一點,如果要做到面向抽象編程,在Java裏有幾個非常重要的語法知識,可以幫助實現抽象面向編程
Interface、Abstract
只有有了接口和抽象類的概念,經常談到的面向對象的三大特性其中之一就是多態性,才能得到很好地支持
做不到面向抽象編程,開閉原則是實現不了的
10、爲什麼實現開閉原則就必須面向抽象編程
創建一個類 Demo
public class Demo {
public String print(){
return "This is a class : Demo";
}
}
創建一個類 Demo2
public class Demo2 {
public String print(){
return "This is a class : Demo2";
}
}
創建主類
public class Main {
public static void main(String[] args) {
Demo demo = new Demo();
System.out.println(demo.print());
}
}
假如沒有Demo2這個類,後續根據業務增加的Demo2
要輸出 Demo2 的東西,直接在Demo中改即可。但這種方法是最糟糕的方案,直接修改了業務類。
既然是開閉原則,那麼直接新增一個Demo2,而不動Demo的實現,這樣看起來像是實現了開閉原則
但問題是Main類也需要實例化一個Demo2,這時候就不符合開閉原則了
複雜的業務場景中都是類與類之間的相互作用
Main 中依賴的 A 是一個具體的類,而不是一個抽象,這個 A 太過於具體,一旦發生變化,和 A 相關的所有代碼都需要更改
不管new 的是什麼類,總是能給到一個方法以調用,這就可以了
11、面向抽象的常見手段:interface、工廠模式與 IOC、DI
- 最開始需要的是 interface 去面向抽象編程,但interface 無法完全解決問題,這是第一階段
- 第二階段就是設計模式中的工廠模式/方法,使用工廠創建方法,從而消除具體的對象
- 第三階段也即出現了 IOC/DI
無論是interface、工廠、IOC,目的是什麼?表面上看是要面向抽象編程,但是面向抽象編程好處是什麼?它們的真實目的是什麼?
面向抽象只是表面上的意義,也即不能依賴一個具體的類,而是要依賴抽象。它們真正的目的是爲了編寫可維護的代碼,實現衆多目的中最重要的 OCP 原則,即開閉原則
12、模擬一個 LOL 小實例
- LOL 從面世到如今已經有 140多個英雄,也即每年平均增加 10 多個
- 除了英雄之外,每個版本的地圖、道具、技能、平衡性、可選英雄等都有頻繁更新
一個是可選英雄的不斷增加,從而擴充英雄池;另一個是開局時必須選擇一個英雄
第一版:
第一個英雄:FirstHeroDiana
public class FirstHeroDiana {
public void q(){
System.out.println("Diana 釋放了 Q 技能");
}
public void w(){
System.out.println("Diana 釋放了 W 技能");
}
public void e(){
System.out.println("Diana 釋放了 E 技能");
}
public void r(){
System.out.println("Diana 釋放了 R 技能");
}
}
選擇了英雄後,釋放技能
Main
public class Main {
public static void main(String[] args) {
String name = Main.getPlayerInput();
//選擇英雄之後,釋放一個技能
if(Objects.equals("Diana", name)){
FirstHeroDiana diana = new FirstHeroDiana();
diana.r();
}
}
private static String getPlayerInput(){
System.out.println("Enter a hero‘s name: ");
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
return name;
}
}
===============================================================================================
第二版,隨着版本迭代,需要添加更多英雄,用戶可以選擇的英雄要多了
第二個英雄:Irelia
public class SecondHeroTrelia {
public void q(){
System.out.println("Trelia 釋放了 Q 技能");
}
public void w(){
System.out.println("Trelia 釋放了 W 技能");
}
public void e(){
System.out.println("Trelia 釋放了 E 技能");
}
public void r(){
System.out.println("Trelia 釋放了 R 技能");
}
}
第三個英雄:ThirdHeroCamille
public class ThirdHeroCamille {
public void q(){
System.out.println("Camille 釋放了 Q 技能");
}
public void w(){
System.out.println("Camille 釋放了 W 技能");
}
public void e(){
System.out.println("Camille 釋放了 E 技能");
}
public void r(){
System.out.println("Camille 釋放了 R 技能");
}
}
第二版的 Main
public class Main {
public static void main(String[] args) {
String name = Main.getPlayerInput();
//選擇英雄之後,釋放一個技能
switch (name){
case "Diana":
FirstHeroDiana diana = new FirstHeroDiana();
diana.r();
break;
case "Irelia":
SecondHeroIrelia irelia = new SecondHeroIrelia();
irelia.q();
break;
case "Camille":
ThirdHeroCamille camille = new ThirdHeroCamille();
camille.e();
break;
}
}
private static String getPlayerInput(){
System.out.println("Enter a hero‘s name: ");
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
return name;
}
}
===============================================================================================
以上存在的問題是,每次新增英雄都要修改 Main 中的代碼,添加新的分支,無法滿足開閉原則,但這種也是最爲習慣的代碼寫法
第三版:使用Interface的抽象風格
新增 interface,讓所有的 Hero 類都實現 interface
ISKill
public interface ISkill {
public void q();
public void w();
public void e();
public void r();
}
第一個英雄:FirstHeroDiana
public class FirstHeroDiana implements ISkill {
@Override
public void q() {
System.out.println("Camille 釋放了 Q 技能");
}
@Override
public void w() {
System.out.println("Camille 釋放了 W 技能");
}
@Override
public void e() {
System.out.println("Camille 釋放了 E 技能");
}
@Override
public void r() {
System.out.println("Camille 釋放了 R 技能");
}
}
第二個英雄:SecondHeroIrelia
public class SecondHeroIrelia implements ISkill{
@Override
public void q() {
System.out.println("Camille 釋放了 Q 技能");
}
@Override
public void w() {
System.out.println("Camille 釋放了 W 技能");
}
@Override
public void e() {
System.out.println("Camille 釋放了 E 技能");
}
@Override
public void r() {
System.out.println("Camille 釋放了 R 技能");
}
}
第三個英雄:ThirdHeroCamille
public class ThirdHeroCamille implements ISkill{
@Override
public void q() {
System.out.println("Camille 釋放了 Q 技能");
}
@Override
public void w() {
System.out.println("Camille 釋放了 W 技能");
}
@Override
public void e() {
System.out.println("Camille 釋放了 E 技能");
}
@Override
public void r() {
System.out.println("Camille 釋放了 R 技能");
}
}
第三版的 Main
public class Main {
public static void main(String[] args) {
String name = getPlayerInput();
ISkill iSkill = null;
switch (name){
case "FirstHeroDiana":
iSkill = new FirstHeroDiana();
break;
case "SecondHeroIrelia":
iSkill = new SecondHeroIrelia();
break;
case "ThirdHeroCamille":
iSkill = new ThirdHeroCamille();
break;
}
try {
iSkill.r();
}catch (Exception e){
e.printStackTrace();
}
}
private static String getPlayerInput(){
System.out.println("Enter a hero‘s name: ");
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
return name;
}
}
===============================================================================================
第四版,原來的Main中存在switch,第三版中還是存在switch,雖然實現了 interface 統一方法調用,但無法統一實例化
第三版 比 第二版看起來差別不大,因爲第三版既沒有把switch 幹掉,也沒有解決開閉原則的問題,如果有第四個英雄出現,還是得在switch中增加一個條件分之,依然是改動了主體代碼,沒有解決根本問題
如果只用一個 interface 就能實現開閉原則,那還要IOC 和DI這些有什麼用呢?
單純的 interface 在某種程度上可以統一方法的調用,但不能統一對象的實例化,抽象的難點在於對象實例化的統一
前面三版代碼,總是要先實例化一個對象,然後再調用對象的方法,這也是面向對象常做的事情,即完成一系列的業務邏輯
如果想讓一段代碼保持穩定,那麼在這段代碼中就不能出現 new 這種操作,這樣才能逐步實現OCP
實質是一段代碼要保存穩定,就不應該負責對象的實例化
對象的實例化過程是不可消除的,可以把對象實例化的過程轉移到其他的代碼片段裏
有時候在代碼中不僅有實例化對象然後調用方法,還經常會給對象賦值(經常是在構造參數裏賦值),但這個操作也可以歸結到對象的實例化,是一個實例化的子項
把對象實例化的過程轉移到其他的代碼片段裏,可以使用設計模式中的工廠模式
工廠模式也分三種子模式:
- 簡單工廠模式
- 普通工廠模式
- 抽象工廠模式
三個 Hero 類以及ISkill 接口不變,新增 HeroFactory 工廠類
public class HeroFactory {
public static ISkill getGero(String name){
ISkill iSkill = null;
switch (name){
case "FirstHeroDiana":
iSkill = new FirstHeroDiana();
break;
case "SecondHeroIrelia":
iSkill = new SecondHeroIrelia();
break;
case "ThirdHeroCamille":
iSkill = new ThirdHeroCamille();
break;
}
return iSkill;
}
}
Main
public class Main {
public static void main(String[] args) {
String name = getPlayerInput();
ISkill iSkill = HeroFactory.getGero(name);
iSkill.e();
}
private static String getPlayerInput(){
System.out.println("Enter a hero‘s name: ");
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
return name;
}
}
如果將來再新增一個Hero,Main中的代碼是否需要更改?對於Main來說,確實完成了OCP,但僅限於Main方法,因爲HeroFactory中的代碼依然需要改動,如果解釋這種情況?
很多資料中提到,使用工廠模式可以解決new的問題,但只是對於工廠來講,還是需要改動代碼,這不是衝突了?是否OCP是一個僞需求呢?
假如HeroFactory.getGero(name); 不是一個靜態方法,而是一個實例方法,這樣在調用該方法時,工廠還是得new
===============================================================================================
第四版中,引入了HeroFactory使得Main的穩定性,但卻使得HeroFactory的代碼不穩定。
HeroFactory 的代碼是不穩定的,新增Hero的時候是需要改動的,但Main是不需要的
穩定具有相對性,代碼中總是會存在着不穩定,所以要儘可能的隔離這些不穩定代碼,保證其他代碼是穩定的。
還是沒有意義?換個角度思考,引入了HeroFactory使得Main的穩定性,但如果有各種各樣的HeroFactory,還是需要改動很多很多的代碼
設想一下,有一個總的工廠,把整個項目所有的變動,都封裝在一起,成爲一個超級的HeroFactory,從而達到整個項目中,除了這個超級工廠外,所有的代碼都是穩定的
代碼中不穩定的本質原因是什麼?軟件總是存在着變化的,正式由於這種變化,造成了代碼中的不穩定
通過反射機制消除所有的變化,計算機裏的代碼,其實是現實世界裏的規律,或者是業務的映射。
使用計算機的代碼模擬現實世界中的業務,從而使用代碼來解決現實世界中的一些業務問題
新建一個 reflect包,將第四版中的英雄包和英雄工廠類都拷貝到reflect下
第五版:修改:HeroFactory(通過反射與元類的方式)
元類:類是對象的抽象,或者類是用來描述一個對象的;元類是用來描述一個類的,通過元類直接實例化一個對象
public class HeroFactory {
public static ISkill getGero(String name) throws Exception {
ISkill iSkill = null;
//"reflect" 表示完整包
String classStr = "reflect" + name;
Class<?> aClass = Class.forName(classStr);
Object o = aClass.newInstance();
return (ISkill)o;
}
}
其他類不改
上述代碼使用了 工廠模式中的簡單模式 + 反射機制 ,但每一次用戶輸入一次,都要反射一次,但反射的性能是比較低的
Spring 當實例化一個對象之後,會把對象放到緩存中,下次再要創建相同對象的時候,直接從緩存中取出
工廠模式+反射並不是IOC和DI,上述實現的僅僅只是使得代碼變得穩定,但沒有應用到 IOC 和 DI ,依然是常規的正向思維
改動配置文件,是否違反OCP?
配置文件不應該理解爲系統的一部分了,配置文件更多的時候是屬於系統外部的文件,而不屬於代碼本身
===============================================================================================
IOC 和 DI
以前是主動向容器要一個對象,現在是反過來,容器主動把對象給出來
爲什麼引入容器後可以讓系統變得穩定?
IOC、DI、DIP
IOC:控制反轉
DI:依賴注入
DIP:依賴倒置(Dependency Inversion Principle)
- 高層模塊(抽象)不應該依賴低層模塊(具體實現),兩者都應該依賴抽象
- 抽象不應該依賴細節
- 細節應該依賴抽象
DI (Dependency Injection)依賴注入的意義
對象與對象之間肯定是要產生依賴的,這個依賴肯定是不可避免的,面向對象就是對象之間相互作用
關鍵是產生這個依賴的方式是有多種多樣的,最常見的就是 new,但這個是最不穩定的
換一種方式,即讓容器把對象給注入進來,這樣也產生了依賴,但產生依賴的形式是不同的,它是注入進來的
- 屬性注入
public class A { private IC ic; private void print(){ this.ic.print(); } public void setIc(IC ic) { this.ic = ic; } }
- 構造注入
public A(IC ic) { this.ic = ic; }
- 接口注入
- .........
動態語言裏是否可以實現依賴注入?動態語言是否能夠實現依賴注入?
動態語言也是有必要實現依賴注入的,同時也可以實現依賴注入
OCP其實是所有語言都需要面對的問題,因爲這是軟甲工程裏需要解決的問題
軟甲工程和語言無關
容器的作用是在裝配對象(依賴注入在更高角度上的意義)
IOC 本身是非常抽象和模糊的,它的具體實現就是DI,
IOC的抽象概念:
- 控制權(對於一個程序的控制權來說,主要的有程序員與用戶,但最主要還是程序員。如果程序需求變化,在增加需求的類,還要更改控制的代碼,所以這些控制代碼還是由程序員控制的)
舉個栗子
想像一下,一個積木生產廠家(程序員),只負責生產和設計一個一個的積木(類),由玩家/用戶搭建各類成品。