Java编程思想

本文章仅表述作者个人观点。内容来源于慕课网七月老师Java全栈课,记录一下自己学习的过程。

很多同学对Java有个误解,认为Java这个语言跟动态语言比起来很笨重。那么Java为什么能这么多年长盛不衰呢?为什么企业里做复杂的大项目会优先选择用Java,而不优先选用动态语言呢?并不是说动态语言不能去写大项目,关键是动态语言写出来的大项目非常难以维护。但是我们用Java或C#这类静态编译型语言来写项目,虽然没有太多像动态语言的灵活性,这主要是为了维护方便。Python和JavaScript等动态语言过于灵活,想写出可维护性高的代码难度较高。

本文所追求的“好代码”为:不啰嗦、有自描述性、可维护性高 的代码。所有软件的复杂性其实都是为了写出可维护性高的代码。软件工程这么多的方法论实际上目的是想解决项目的维护和迭代

那么如何实现软件的可维护性,这就要谈到软件工程中的几个原则,比如开闭原则、里式替换原则、迪米特法则等。其中最为重要的就是开闭原则,其他原则都是为了实现开闭原则。开闭原则(Open Closed Principle),简称OCP,对于扩展是开放的,对于修改是封闭的。

实现OCP最基础的准则就是面向抽象编程。在Java中有几个基础语法可以帮助我们实现面向抽象编程,其中两个是interface和abstract,有了接口和抽象类,面向对象的三大特性之一——多态性才能够得到很好的支持。

本文最终目的是为了引出IOC和DI,及他们有什么用。IOC和DI这个概念也不是一下子凭空出来的,而是一步一步演进出来的。
interface
设计模式:工厂模式
IOC/DI
面向抽象 为了实现 OCP ,最终的目的是实现 可维护的代码

代码的演进过程

接下来的几版代码由最原始的代码逐步实现OCP

第一版

令人无语的代码风格

public class Diana {
    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"); } 
}

public class Irelia {
    public void q(){ System.out.println("Irelia Q"); }
    public void w(){ System.out.println("Irelia W"); }
    public void e(){ System.out.println("Irelia E"); }
    public void r(){ System.out.println("Irelia R"); }
}

public class Camille {
    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"); }
}
public class Main {

    public static void main(String[] args) {
        String name = Main.getPlayerInput();
        switch (name) {
            case "Diana":
                Diana diana = new Diana();
                diana.r();
                break;
            case "Irelia":
                Irelia irelia = new Irelia();
                irelia.r();
                break;
            case "Camille":
                Camille camille = new Camille();
                camille.r();
                break;
        }
    }
    private static String getPlayerInput(){
        System.out.println("Enter a Hero's Name:");
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }
}

第二版

使用Interface的抽象风格

package abstraction;
public interface ISkill {
    void q();
    void w();
    void e();
    void r();
}
package abstraction.hero;
import abstraction.ISkill;
public class Diana implements ISkill {
    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"); }
}

public class Irelia implements ISkill {
    public void q(){ System.out.println("Irelia Q"); }
    public void w(){ System.out.println("Irelia W"); }
    public void e(){ System.out.println("Irelia E"); }
    public void r(){ System.out.println("Irelia R"); }
}

public class Camille implements ISkill {
    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"); }
}
   public static void main(String[] args) throws Exception {
        String name = Main.getPlayerInput();
        ISkill iSkill;
        switch (name) {
            case "Diana":
                iSkill = new Diana();
                break;
            case "Irelia":
                iSkill = new Irelia();
                break;
            case "Camille":
                iSkill = new Camille();
                break;
            default:
                throw new Exception();
        }
        iSkill.r();
    }

第二版相比于第一版代码,使用Interface的抽象风格统一了代码的调用,但是单纯的interface无法统一对象的实例化,仍然不能完全实现OCP。其实,无论是多种多样的设计模式还是IOC与DI都是在解决对象实例化统一的问题。

第三版

工厂模式分离对象的实例化

Diana Irelia Camille ISkill 同第二版,新增HeroFactory用来实例化对象

public class HeroFactory {

    public static ISkill getHero(String name) throws Exception {
        ISkill iSkill;
        switch (name) {
            case "Diana":
                iSkill = new Diana();
                break;
            case "Irelia":
                iSkill = new Irelia();
                break;
            case "Camille":
                iSkill = new Camille();
                break;
            default:
                throw new Exception();
        }
        return iSkill;
    }
}
public class Main {

    public static void main(String[] args) throws Exception {
        String name = getPlayerInput();
        ISkill iSkill = HeroFactory.getHero(name);
        iSkill.r();
    }
    private static String getPlayerInput(){
        System.out.println("Enter a Hero's Name:");
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }
}

第三版相比于第二版代码,将对象的实例化封装到HeroFactory,使main方法变得相对稳定,但是HeroFactory仍然是不稳定的。当一段代码中没有了new的出现,也就是不负责对象的实例化,才能保持代码的相对稳定。但是,在面向对象编程中实质上主要就是在做两件事情:实例化对象调用方法。实例化对象是不可能消除的,变化也总是存在的,代码的不稳定也就一定会存在。隔离这些不稳定,保持其他代码是相对稳定的。

第四版

通过反射机制消除所有的变化

这版代码只修改HeroFactory 其他代码同第三版

public class HeroFactory {

    public static ISkill getHero(String name) throws Exception {
        ISkill iSkill;
        String classStr = "reflect.hero."+name;
        Class<?> cla = Class.forName(classStr);
        Object obj = cla.newInstance();
        return (ISkill)obj;
    }
}

Spring的IOC其实和此处使用的原理是一样的,Spring内部也是使用了工厂模式+反射机制。但是Spring的IOC比我们这要更加完善,我们的反射每次用户输入都要去反射,性能较低。而Spring实例化一个对象后会放到它的缓存中去,下次再需要相同的对象时Spring就会直接从缓存中把对象给我们了。

重点理论

1.单纯interface可以统一方法调用,但是不能统一对象的实例化。
2.面向对象 主要就是在做这两件事情:实例化对象 调用方法(完成业务逻辑)
3.只有一段代码中没有new的出现,才能保持代码的相对稳定,才能逐步实现OCP
4.上面的这句话只是表象,实质是一段代码如果要保持稳定,就不该负责对象的实例化。
5.对象的实例化是不可能消除的
6.把对象实例化的过程,转移到其他的代码片段里
7.代码总是会存在不稳定,隔离这些不稳定,保持其他的代码是稳定的。
8.变化造成了不稳定

面向对象编程,所有的变化最终都要交给不同的对象去处理,当一些业务或者用户的输入有了变化的时候必须要创建不同的对象去响应用户的输入,这也就是为什么我们说一段代码只要出现了new就是导致这段代码不稳定的原因。

9.配置文件属于系统外部的,而不属于代码本身

IOC与DI

以上的代码虽然也使用了工厂模式+反射机制,实现了OCP,但只是让代码变得相对稳定,并没有体现出IOC与DI,依然是常规的正向思维,需要什么就去调用哪个类下的哪个方法。而Spring的IOC远远不只是实现了OCP这么简单。
上面的代码在每次需要ISkill对象的时候都需要引入HeroFactory ,再通过工厂下面的方法获取对象,虽然把对象实例化的工作封装起来了,但是使用起来并不方便。不想用new也不想用HeroFactory ,那么如何能让ISkill变的有值呢?Spring的IOC机制就可以做到。IOC和DI要做的就是让HeroFactory都不用出现,让我们可以直接拿到ISkill

public class A{
	private C c;
	public void print(){
		C c = new C();
		c.print();
	}
}

如上代码,A对C产生依赖,当系统非常复杂时系统中各种各样的类相互之间产生的依赖会非常复杂,耦合度非常高,没有办法很好的实现OCP。现在引入容器的概念,当我们需要C时有容器主动把c给我们。

为什么由容器把对象给我们会更加稳定?
我们可以把我们的系统想象成如下图中A、B、C、D四个齿轮,每个齿轮相当于面向对象中的类。图一中的四个齿轮紧紧的耦合在一起,相互依赖,任何一个齿轮出现问题系统就运转不了了。图二中引入容器,就像中间那个超大的齿轮,整个系统靠容器带动,A、B、C、D四个齿轮其中一个出问题或出现变动不会影响到其他齿轮,形成了松耦合。
在这里插入图片描述

IOC 、DI 、DIP

DIP(Dependency Inversion Principle)依赖倒置

High level modules should not depend upon low level modules,both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.
高层模块不应该依赖低层模块,两者都应该依赖抽象
抽象不应该依赖细节。细节应该依赖抽象。

DI(Dependency Injection)依赖注入

面向对象就是对象与对象之间相互作用,从而构成整个系统。对象与对象的相互作用必定会产生依赖,关键是生成依赖的方式是多种多样的。以前是我们主动去获取对象,现在有一个超大的容器,实例化好了各个对象,当有某个对象需要另一对象时,容器将所需对象注入到依赖对象中。

注入的方式有很多种
1.属性注入

public class A{
	private IC ic;
}

2.构造注入

public class A{
	public A(IC ic){};
}

3.接口注入

容器实际上是在装配 一个个的类,我们在编写一个个类的时候是完全独立的,把装配的过程交给容器,容器把变化隔离起来。具体实例化哪个对象不知道,全部由容器来决定,这样就可以保证系统非常稳定。

IOC(Inversion of Control)控制反转

IOC的概念是非常抽象和模糊的,只是讲述一种思想并没有给出具体的实现,所以Martin Folwer给出了IOC的一种具体实现——DI
原来主控类是A,现在有了IOC,主控方变成了container

控制反转的理解:

变化会造成程序的不稳定。
IOC 控制权 的反转 以前由我们程序员来控制,现在由用户来控制

小例子:
假设我们是积木生产厂家(程序员)只负责生产一个一个积木,玩家/用户想要如何组装交给用户。
在编程中,程序员只负责写一个一个的类,至于这些类用哪个,怎么用,交给产品经理或其他人来决定,就是控制反转。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三毛村滴雪鱼粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值