Java 開發思考總結(一)

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、怎麼樣才能叫好維護或者說怎麼樣可以稱之爲可維護的代碼?

軟件工程裏面必要重要的幾個原則:

  1. 開閉原則(最重要的,可維護代碼的基礎)
  2. 里氏替換原則
  3. 迪米特法則

多數原則其實都是爲了實現開閉原則,比如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

以前是主動向容器要一個對象,現在是反過來,容器主動把對象給出來

爲什麼引入容器後可以讓系統變得穩定?

https://www.iteye.com/blog/1141338892-2306698

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的抽象概念:

  • 控制權(對於一個程序的控制權來說,主要的有程序員與用戶,但最主要還是程序員。如果程序需求變化,在增加需求的類,還要更改控制的代碼,所以這些控制代碼還是由程序員控制的)

舉個栗子

想像一下,一個積木生產廠家(程序員),只負責生產和設計一個一個的積木(類),由玩家/用戶搭建各類成品。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章