我们日常使用的电脑主机,把cpu、内存、主板等等都封装到机箱里面去。假如没有机箱的话的出现什么问题,主机、主板全部都散落在一处,然后开机没有开机按钮,那么需要我们直接操作接跳线才能把电脑开启。这样子的话假如操作不慎的话,会让机器损坏危险,那么假如用机箱封装起来的话,那么就不需要这样子做了。体现了封装的—安全特性。
你拿电脑去加内存,可以直接给电脑给维修的人,等他加好内存了之后。你拿到的还是那个机箱,里面发生了怎样子的变化你并不知道。封装的第二个好处-将变化隔离。
在机箱里面提供一个开机按钮,而不需要你直接使用跳线开机的话,体现了封装的—便于使用的特性。
只要机箱提供了一个开机的功能,然后无论这个机箱拿到哪里去,都可以使用这个开机的功能.体现了封装的—提供重复性的特性。
对比一下没有封装和有封装~
没有封装的时候
- 描述Employee类。定义姓名,工号,性别的成员变量,和工作的方法。成员使用public修饰。
- 创建Employee对象,对象.成员的方式进行赋值。最后该对象调用工作方法。
- 总结:如果不使用封装,很容易赋值错误,并且任何人都可以更改,造成信息的 不安全。
- 问题解决:使用封装
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class EmployeeDemo {
public static void main(String[] args) {
// 创建对象
Employee jack = new Employee();
// 进制通过类名.成员的形式调用成员。初始化实例变量
jack.name = "jack";
jack.id = "123456";
jack.gender = "男";
// 调用成员方法
jack.work();
System.out.println();
// 传入非法的参数
jack.gender = "不是男人";
jack.work();
}
}
class Employee {
String name;
String id;
String gender;
public void work() {
System.out.println(id + ":" + name + ":" + gender + " 努力工作中!!!");
}
}
有封装的时候
设置类的属性为private(关键字),不能使用对象名.属性名的方式直接访问对象的属性。
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class EmployeeDemo {
public static void main(String[] args) {
// 创建对象
Employee jack = new Employee();
//编译报错
jack.name = "jack";
jack.id = "123456";
jack.gender = "男";
// 编译报错
jack.gender = "不是男人";
jack.work();
}
}
class Employee {
//使用了private修饰了成员变量
private String name;
private String id;
private String gender;
public void work() {
System.out.println(id + ":" + name + ":" + gender + " 努力工作中!!!");
}
}
修改Employee类 性别的修饰符修改为private
1:编译不通过
2:private修饰的成员在自己所在的类中可以使用,在类外边不可以使用。
3:Employee类的gender的修饰符修改为private后,无法再类外调用,那么如何给gender设置值?
1:对外提供公开的用于设置对象属性的public方法
1:设置set
2:获取get
2:在set方法中加入逻辑判断,过滤掉非法数据。
3:将所有的成员变量封装加上private,提供get、set方法
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class EmployeeDemo {
public static void main(String[] args) {
// 创建对象
Employee jack = new Employee();
// 调用公有方法,给成员变量赋值。
jack.setId("007");
jack.setName("jack");
jack.setGender("男xx");
// 获取实例变量的值
System.out.println(jack.getGender());
System.out.println(jack.getId());
System.out.println(jack.getName());
// 调用成员方法
jack.work();
}
}
class Employee {
private String name;
private String id;
private String gender;
// 提供公有的get set方法
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
public String getId() {
return id;
}
public void setId(String i) {
id = i;
}
public String getGender() {
return gender;
}
public void setGender(String gen) {
if ("男".equals(gen) || "女".equals(gen)) {
gender = gen;
} else {
System.out.println("请输入\"男\"或者\"女\"");
}
}
public void work() {
System.out.println(id + ":" + name + ":" + gender + " 努力工作中!!!");
}
}
1:隐藏了类的具体实现
2:操作简单
3:提高对象数据的安全性
写一个例子吧~
描述一个计算器类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
class Calculator
{
// 1. 查看具体的计算器对象抽取所有计算器具有的共同属性
public String name = "我的计算器我做主";
public double num1;
public double num2;
public char option;
// 2. 查看具体的计算器对象抽取所有计算器具有的共同功能
// 2.1 定义接受数据的功能函数
public void init( double a , char op , double b ){
num1 = a;
option = op;
num2 = b;
}
// 2.2 定义计算的功能
public void calculate(){
switch ( option )
{
case '+': System.out.println( name + " : " + num1 + " + " + num2 + " = " + ( num1 + num2 ) );
break;
case '-': System.out.println( name + " : " + num1 + " - " + num2 + " = " + ( num1 - num2 ) );
break;
case '*': System.out.println( name + " : " + num1 + " * " + num2 + " = " + ( num1 * num2 ) );
break;
case '/': {
if( num2 != 0 )
System.out.println( name + " : " + num1 + " / " + num2 + " = " + ( num1 / num2 ) );
else
System.out.println("除数不能为0!");
break;
}
case '%': {
// 1.处理结果的符号问题,使得结果的符号满足数学的要求
// 2.解决NaN的问题
System.out.println( name + " : " + num1 + " % " + num2 + " = " + ( num1 % num2 ) );
break;
}
default : System.out.println("你在捣乱,我不理你,气死你......");
}
}
}
class Demo9
{
public static void main(String[] args)
{
Calculator cal = new Calculator();
cal.init( 41 , '%' , 0 );
cal.calculate();
System.out.println("计算完毕!再来一次......");
}
}
继承可以让类与类之间产生关系,父子类关系,产生子父类后,子类则可以使用父类中非私有的成员
优点:
1.提高了代码的复用性(多个类相同的成员可以放到同一个父类中)
2. 提高了代码的维护性(如果代码需要修改,修改父类一处即可)
3. 是多态的前提
缺点:
降低了代码的灵活性,增强了代码的耦合性(依赖性越强耦合性越强)
继承通过extends实现
格式:class 子类 extends 父类 { }
举例:class Dog extends Animal { }
1.类与类之间存在共性
2.并且产生了is a关系
1.如果父类中有无参的构造方法时,调用子类任意构造方法都会访问父类中无参的构造方法
为什么?
子类继承父类的时候,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化
2.如何初始化父类?
调用构造方法,因此每个子类构造方法的第一条语句默认都是:super()
3.注意事项
如果类没有指明父类,默认继承自Object类,Objec类是所有java类的父类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
package com.itheima.constructor.empty;
public class Person {
private String name;
private String age;
int num=10;
public Person() {
System.out.println("我是父类的空参数构造方法");
}
public Person(String name, String age) {
this.name = name;
this.age = age;
System.out.println("我是父类中的带参构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
package com.itheima.constructor.empty;
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class Student {
//子类特有的属性
private int score;
public Student() {
//子类在初始化前,一定要先完成父类数据的初始化
//子类在初始化之前,一定要先访问到父类的构造方法,完成父类的初始化
//系统在每个构造方法中,默认隐藏一个super()
super();
System.out.println("我是子类的空参数构造方法......");
}
public Student(int score) {
super();
this.score = score;
System.out.println("我是子类中带参数构造方法!!!");
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
package com.itheima.constructor.empty;
public class Test {
public static void main(String[] args) {
Student student = new Student();
Student student1 = new Student(10);
}
}
1.如何处理?
通过super(…)手动的调用父类中带参构造方法
任意类中都手动的提供一个无参构造方法
2.注意事项
this(…)和super(…)必须在构造方法的第一行
两者不能共存
1.Java中类只支持单继承(extends 一个父类),不支持多继承(extends 多个父类)
2.Java中类支持多层继承(一个子类可以有一个父类,一个父类还可以有一个父类)
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。用专业术语讲,就是同一个对象(父类对象),在不同时刻表现出来的不同形态。
- 要有继承或实现关系
- 要有方法的重写
- 要有父类引用指向子类对象
- 好处
提高程序的扩展性。定义方法时候,使用父类型作为参数,该方法就可以接收父类的任意子类对象- 弊端
不能使用子类的特有功能
- 风险
如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException- 解决方案
- 关键字
instance of- 使用格式
变量名 instanceof 类型
通俗的理解:判断关键字左边的变量,是否是右边的类型,返回boolean类型结果
单一职责原则SRP(Single Responsibility Principle)
是指一个类的功能要单一,不能包罗万象。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。
● 类的复杂性降低,实现什么职责都有清晰明确的定义;
● 可读性提高,复杂性降低,那当然可读性提高了;
● 可维护性提高,可读性提高,那当然更容易维护了;
● 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
分化太细,会导致类膨胀
例如:
我们有两个通信设备,一个智能手机,一个传统的座机。 他们拔通电话,挂断电话的方法是一样的。 但是通话方法是不一样的。 手机类可以实现这两个接口,座机类也以实现这两个接口。或者一个接口。 这样两个通信设备的连接方法不一样, 我们只需要重写通信方法就可以了。
开放封闭原则OCP(Open-Close Principle)
一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。比如:一个网络模块,原来只服务端功能,而现在要加入客户端功能,
那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码,这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。
实际上,绝对封闭的系统是不存在的。无论模块是怎么封闭,到最后,总还是有一些无法封闭的变化。而我们的思路就是:既然不能做到完全封闭,那我们就应该对那些变化封闭,那些变化隔离做出选择。我们做出选择,然后将那些无法封闭的变化抽象出来,进行隔离,允许扩展,尽可能的减少系统的开发。当系统变化来临时,我们要及时的做出反应。
我们并不害怕改变的到来。当变化到来时,我们首先需要做的不是修改代码,而是尽可能的将变化抽象出来进行隔离,然后进行扩展。面对需求的变化,对程序的修改应该是尽可能通过添加代码来实现,而不是通过修改代码来实现。
繁忙的银行业务员
根据代码会发现,如果增加了其他业务员,就需要在BankWorker中添加一个业务方法,也让银行业务员“业务”繁忙,容易混乱,扩展性也不好
代码实现:
BankWoker类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class BankWorker {
//负责存款业务
public void saving() {
System.out.println("进行存款业务");
}
//负责取款业务
public void drawing(){
System.out.println("进行取款业务");
}
//负责转账业务
public void transfering(){
System.out.println("进行转账业务");
}
//负责基金申购
public void buying(){
System.out.println("进行基金购买");
}
}
MainClass类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class MainClass {
public static void main(String[] args){
BankWorker bankWorker = new BankWorker();
//存款
bankWorker.saving();
//取款
bankWorker.drawing();
}
}
将银行业务员设置为接口,下面为实现的子类,分为存款业务员、取款业务员、转账业务员等,将业务逻辑分割,改造后分工明确,各施其职,不容易混乱。
例如下面需要增加业务的时候,只需要继续添加子类进行扩展,实现了开放封闭原则,没有大面积修改之前的代码,
BankWorker类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
//银行业务员
public interface BankWorker {
public void operation();
}
SavingBankWorker类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
//负责存款业务员
public class SavingBankWorker implements BankWorker{
public void operation() {
System.out.println("进行存款业务");
}
}
DrawingBankWorker类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
//负责取款业务员
public class DrawingBankWorker implements BankWorker {
public void operation() {
System.out.println("进行取款业务");
}
}
TransferingBankWorker类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
//负责转装业务员
public class TransferingBankWorker implements BankWorker {
public void operation() {
System.out.println("进行转装业务");
}
}
MainClass类
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class MainClass {
public static void main(String[] args){
//改造后分工明确,各施其职,不容易混乱
BankWorker bankWorker = new SavingBankWorker();
bankWorker.operation();
BankWorker bankWorker1 = new DrawingBankWorker();
bankWorker1.operation();
}
}
- 通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,是变化中的软件有一定的适应性和灵活性。
- 已有的软件模块,特别是最重要的抽象模块不能再修改,这就使变化中的软件系统有一定的稳定性和延续性。
里氏代换原则( Liskov Substitution Principle)是指:一个软件实体如果使用的是基类的话, 那么也一定适用于其子类, 而且它根本觉察不错使用的是基类对象还是子类对象; 反过来的代换这是不成立的, 即: 如果一个软件实体使用一个类的子类对象,那么它不能够适用于基类对象。 - 里氏代换原则是实现开放封闭原则的具体规范。这是因为: 实现开放封闭原则的关键是进行抽象,而继承关系又是抽象的一种具体实现,这样LSP就可以确保基类和子类关系的正确性,进而为实现开放封闭原则服务。如图:
法海的任务是降妖除魔、造福众生。凡是妖魔现身,法海必定全力诛灭之
妖魔基类:
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public abstract class Spirit {
/*
* 妖魔的行为方法
*/
public abstract void say();
}
蛇妖基类:
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public abstract class Snake extends Spirit{
/*
* 蛇妖的基类,继承妖魔,可以扩展方法属性
*/
@Override
public void say() {
}
}
白蛇类:
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class WhiteSnake extends Snake{
/*
* 白蛇对蛇妖的继承实现
*/
@Override
public void say() {
System.out.print("我是白蛇");
}
}
测试:
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
public class Test{
public static void main(String[] args){
Spirit spirit=new WhiteSnake();
spirit.say();
}
}
程序运行结果:
我是白蛇
里氏代换原则是很多其它设计模式的基础。它和开放封闭原则的联系尤其紧密。违背了里氏代换原则就一定不符合开放封闭原则。
定义:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
问题:
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来完成。此时,假如类A是高层次的模块,如果修改则会引起不必要的风险。
解决方案:
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接的与类B或者类C发生联系,此时会降低修改A的几率。
理解:
在编程的过程中,需要尽量的提炼出抽象的模型,抽象的东西是稳定的,在Java中体现为接口以及抽象类,我们需要实现的功能则是通过实现接口或者抽象类的方法来实现。
依赖倒置的核心就是面向接口编程。
定义接口:
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
interface IReader{
public String getContent();
}
实现功能:
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
class Newspaper implements IReader {
public String getContent(){
return "我会读报纸";
}
}
class Book implements IReader{
public String getContent(){
return "我会读书";
}
}
class Mother{
public void narrate(IReader reader){
System.out.println("妈妈开始讲故事");
System.out.println(reader.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
mother.narrate(new Newspaper());
}
}
这样修改无论是让妈妈实现什么功能,则无需修改Mother类。
(1).低层模块尽量都要有抽象类或接口,或者两者都有。
(2).变量的声明类型尽量是抽象类或接口。
(3).使用继承时遵循里氏替换原则。
定义:
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类C来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决方案:
将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
举例来说明接口隔离原则:
( 图1 未遵循接口隔离原则的设计)
理解:
类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现。类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。对类图不熟悉的可以参照程序代码来理解,代码如下:
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
interface I {
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}
class A{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method2();
}
public void depend3(I i){
i.method3();
}
}
class B implements I{
public void method1() {
System.out.println("类B实现接口I的方法1");
}
public void method2() {
System.out.println("类B实现接口I的方法2");
}
public void method3() {
System.out.println("类B实现接口I的方法3");
}
//对于类B来说,method4和method5不是必需的,但是由于接口A中有这两个方法,
//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
public void method4() {
}
public void method5() {
}
}
class C{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method4();
}
public void depend3(I i){
i.method5();
}
}
class D implements I{
public void method1() {
System.out.println("类D实现接口I的方法1");
}
//对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,
//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
public void method2() {
}
public void method3() {
}
public void method4() {
System.out.println("类D实现接口I的方法4");
}
public void method5() {
System.out.println("类D实现接口I的方法5");
}
}
public class Client{
public static void main(String[] args){
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend2(new D());
c.depend3(new D());
}
}
可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口,拆分后的设计如图2所示:
(图2 遵循接口隔离原则的设计)
照例贴出程序的代码,供不熟悉类图的朋友参考:
下面展示一些 内联代码片
。
/**
* @Author: NanQiang
* @Date: 2020/7/15 11:18
*/
interface I1 {
public void method1();
}
interface I2 {
public void method2();
public void method3();
}
interface I3 {
public void method4();
public void method5();
}
class A{
public void depend1(I1 i){
i.method1();
}
public void depend2(I2 i){
i.method2();
}
public void depend3(I2 i){
i.method3();
}
}
class B implements I1, I2{
public void method1() {
System.out.println("类B实现接口I1的方法1");
}
public void method2() {
System.out.println("类B实现接口I2的方法2");
}
public void method3() {
System.out.println("类B实现接口I2的方法3");
}
}
class C{
public void depend1(I1 i){
i.method1();
}
public void depend2(I3 i){
i.method4();
}
public void depend3(I3 i){
i.method5();
}
}
class D implements I1, I3{
public void method1() {
System.out.println("类D实现接口I1的方法1");
}
public void method4() {
System.out.println("类D实现接口I3的方法4");
}
public void method5() {
System.out.println("类D实现接口I3的方法5");
}
}
接口隔离原则的含义是:
建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
采用接口隔离原则对接口进行约束时,要注意以下几点:
• 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
• 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
• 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。