Java 开发思考总结(一)

简介: 一个语言很笨重,不是研发者要故意让这个语言变得很笨重,现在语言这么多,如果没有它自己的优势,是很难存活的。在只了解了一些JS、Python之类的动态语言之后,就盲目的下结论是不正确的。Java 笨重是有笨重的原因的,这是因为Java通常是用来做企业级的项目,或者说是复杂的大项目。

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


控制权(对于一个程序的控制权来说,主要的有程序员与用户,但最主要还是程序员。如果程序需求变化,在增加需求的类,还要更改控制的代码,所以这些控制代码还是由程序员控制的)

举个栗子


想像一下,一个积木生产厂家(程序员),只负责生产和设计一个一个的积木(类),由玩家/用户搭建各类成品。

目录
相关文章
|
1月前
|
Java API Maven
如何使用Java开发抖音API接口?
在数字化时代,社交媒体平台如抖音成为生活的重要部分。本文详细介绍了如何用Java开发抖音API接口,从创建开发者账号、申请API权限、准备开发环境,到编写代码、测试运行及注意事项,全面覆盖了整个开发流程。
117 10
|
1月前
|
监控 Java API
如何使用Java语言快速开发一套智慧工地系统
使用Java开发智慧工地系统,采用Spring Cloud微服务架构和前后端分离设计,结合MySQL、MongoDB数据库及RESTful API,集成人脸识别、视频监控、设备与环境监测等功能模块,运用Spark/Flink处理大数据,ECharts/AntV G2实现数据可视化,确保系统安全与性能,采用敏捷开发模式,提供详尽文档与用户培训,支持云部署与容器化管理,快速构建高效、灵活的智慧工地解决方案。
|
21天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
38 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
8天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
55 13
|
13天前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
47 10
|
7天前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
43 2
|
16天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
42 2
|
1月前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
1月前
|
IDE Java 编译器
开发 Java 程序一定要安装 JDK 吗
开发Java程序通常需要安装JDK(Java Development Kit),因为它包含了编译、运行和调试Java程序所需的各种工具和环境。不过,某些集成开发环境(IDE)可能内置了JDK,或可使用在线Java编辑器,无需单独安装。
66 1
下一篇
DataWorks