Scott の 博客 Scott の 博客
首页
  • Data Structure and Algorithm
  • Java
  • 面试
  • Drafts
  • C++
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Scott

恋爱中
首页
  • Data Structure and Algorithm
  • Java
  • 面试
  • Drafts
  • C++
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Data Structure and Algorithm

  • Java

    • Java基础知识&面试题总结
    • Java
    • Control Flow
    • Clean Coding
    • Debugging and Deployment
    • Untitled
    • Refactor towards OOSD
    • Inheritance
    • Untitled
      • Interface
      • Tightly-coupled code
      • Dependency injection
        • Constructor Injection
        • Setter Injection
        • Method Injection
      • Interface Segregation Principle (ISP)
      • Example
      • Bad Feature: Declare fields, static or private methods in interface
        • Declare fields in interface
        • Declare static methods in interface
        • Declare private methods in interface
      • Interfaces VS abstract classes
      • Should we extract an interface from every class?
        • Benefits of Interfaces
    • Exceptions
    • Generics
    • Collections
    • Lambda-Expression
    • Streams
    • Concurrency and Multi-threading
    • The Executive Framework
    • 4
    • 1
  • c++

  • 面试

  • Bilibili_Java

  • Python

  • All kinds of Drafts

  • High Integrity Information System

  • 左神算法课

  • 个人笔记
  • Java
Scott
2021-12-27
目录

Untitled

  1. Interface
  2. Tightly-coupled code
  3. Dependency injection
    1. Constructor Injection
    2. Setter Injection
    3. Method Injection
  4. Interface Segregation Principle (ISP)
  5. Example
  6. Bad Feature: Declare fields, static or private methods in interface
    1. Declare fields in interface
    2. Declare static methods in interface
    3. Declare private methods in interface
  7. Interfaces VS abstract classes
  8. Should we extract an interface from every class?
    1. Benefits of Interfaces
      1. Update Implementations for better performance
      2. Extend Applications with minimum impact
      3. Test classes in isolation
      4. Reminder

# Interface

image-20211227161548729
  • We use interfaces to build loosely-coupled, extensible and testable applications.
public interface Draggable {
    void drag();
}
1
2
3
  • Interface cannot be instantiated like a class
public static void main(String[] args) {
	var c = new TaxCalculator() // not allowed 
}
1
2
3

Static Contract

  • Classes that implements interfaces are coupled with its contract

  • We should make sure that we do not break the contract frequently

    • Because other classes that depend on the contract will be broken
  • One should be careful when designing the contract

  • Make sure the interface is small and lightweight so they are less likely to change

  • It only contains method declarations, no implementations

    • It has no implementation code
    • It only defines the capabilities that a class should have
image-20211227161254339
  • If we change code in B, A will not be affected, because it knows nothing about B.
    • That's what called Programming against Interfaces

To create an interface:

  • Open the Create New Class window
  • Change the kind to Interface
image-20211228152638667

Naming convention (prefix/suffix)

  • CanXXXXXX
    • CanCalculateTax
  • XXXable
    • Draggable

Example:

public interface TaxCalculator {
    // public is redundent, they are all public  
    double calculateTax() 
}

public class TaxCalculator2018 implements TaxCalculator {
    private double taxableIncome;

    public TaxCalculator2018(double taxableIncome) {
    	this.taxableIncome = taxableIncome;
  	}
    
    // should use the override key word
    @Override
    public double calculateTax() {
        return taxableIncome * 0.3;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Tightly-coupled code

  • Tightly-coupled code is code that is hard to change because there is a strong dependency between the entities (eg classes) in the code. Changing one class may result in several cascading, breaking changes in the code.
public class TaxCalculator {
    private double taxableIncome; 
    
    // // if we add new fields to this method, we need to change the TaxReport class too
    public TaxCalculator(double taxableIncome) {
        this.taxableIncome = taxableIncome;
    }
    
    public double calculateTax() {
        return taxableIncome * 0.4; // if we change this number, other classes will need to be recompiled and redeployed 
    }
}

public class TaxReport {
	private TaxCalculator calculator;

    public TaxReport() {
        this.calculator = TaxCalculator(10_000); // only has one field, but what if we want to add more fields?
    }

    public void show(TaxCalculator calculator) {
        var tax = calculator.calculateTax();
        System.out.println(tax);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface TaxCalculator {}

public class TaxCalculator2018 implements TaxCalculator {}

public class TaxReport { 
    private TaxCalculator calculator;
    public TaxReport() { 
        calculator = new TaxCalculator2018();
    }
}
1
2
3
4
5
6
7
8
9
10
  • Even though the type of the calculator field in TaxReport is an interface, we’re initializing this field to an instance of TaxCalculator2018 in the constructor
  • So, TaxReport is tightly coupled to TaxCalculator2018, which is an implementation, not an interface.

# Dependency injection

  • Dependency injection refers to passing or injecting dependencies of a class.

  • We can inject dependencies via

    • constructors
    • setters
    • and regular methods
  • Our classes should not instantiate their dependencies

public class TaxReport { 
    private TaxCalculator calculator;
    
    // should not instantiate their dependencies 
    // should just use it (separation of concerns )
    public TaxReport() { 
        calculator = new TaxCalculator2018();
    }
}
1
2
3
4
5
6
7
8
9

# Constructor Injection

  • Don't want the class to be dependent on concrete implementation
  • We want it to be dependent on interfaces
  • So we use a constructor to inject an interface dependency
// before
public class TaxReport { 
    private TaxCalculator2018 calculator;
    public TaxReport() { 
        calculator = new TaxCalculator2018();
    }
}

// after 
public class TaxReport { 
    private TaxCalculator calculator;
    
    // constructor injection 
    public TaxReport(TaxCalculator calculator) { 
        this.calculator = calculator;
    }
}

// loose coupling between TaxReport and TaxCalculator2018
// we can easily modify/switch TaxCalculator2018, and TaxReport will not be affected 
// One can use frameworks like Spring to manage those dependencies more effectively 
public static void main(String[] args) {
	var calculator = new TaxCalculator2018(10_000); // not allowed 
    var report = new TaxReport(calculator);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Setter Injection

// before
public class TaxReport { 
    private TaxCalculator2018 calculator;
    public TaxReport() { 
        calculator = new TaxCalculator2018();
    }
}


// after 
public class TaxReport { 
    private TaxCalculator calculator;
    
    // constructor injection 
    public TaxReport(TaxCalculator calculator) { 
        this.calculator = calculator;
    }
    
    // we can change the dependency throughout the lifetime of the program 
    public void setCalculator(TaxCalculator TaxCalculator) {
        this.calculator = calculator;
    }
    
    public void show() {
        var tax = calculator.calculateTax();
        System.out.println(tax);
    }
}

// new calculator
public class TaxCalculator2019 implements TaxCalculator {
  @Override
  public double calculateTax() {
    return 0;
  }
}


public static void main(String[] args) {
	var calculator = new TaxCalculator2018(10_000); // not allowed 
    var report = new TaxReport(calculator);
    report.show();
    
    // change the calculator
    report.setCalculator(new TaxCalculator2019());
    report.show(); // will get different result 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# Method Injection

// before
public class TaxReport { 
    private TaxCalculator2018 calculator;
    public TaxReport() { 
        calculator = new TaxCalculator2018();
    }
}


// after 
public class TaxReport { 
    private TaxCalculator calculator;
    
    public void show(TaxCalculator calculator) {
        var tax = calculator.calculateTax();
        System.out.println(tax);
    }
}

public static void main(String[] args) {
	var calculator = new TaxCalculator2018(10_000); // not allowed 
    var report = new TaxReport();
    
    // Pass dependence to method
    report.show(calculator);
    report.show(new TaxCalculator2019()); // will get different result 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Interface Segregation Principle (ISP)

  • The Interface Segregation Principle (ISP) suggests that we should segregate or divide big, fat interfaces into smaller ones, each focusing on a single responsibility or capability
  • Smaller interfaces are less likely to change
  • Changes to one capability, will only affect a single interface and fewer classes that depend on that interface.
public interface UIWidget {
    void drag();
    void resize();
    void render();
}

public class Dragger {
    public void drag(UIWidget widget) {
        widget.drag();
        System.out.println("Drag");
    }
}

// should be 
public interface Draggable {
    void drag();
}

public class Dragger {
    public void drag(Draggable draggable) {
        draggable.drag();
        System.out.println("Drag");
    }
}

//----------------------

// use inheritance 
// interface can have multiple parents, because methods are not implementations
// there are no ambiguity
public interface UIWidget extends Draggable, Resizable {
    void render();
}

public class Dragger {
    public void drag(UIWidget draggable) {
        draggable.drag();
        System.out.println("Drag");
    }
}

public interface Resizable {
    void resize(int size);
    void resize(int x, int y);
    void resizeTo(UiWidget widget);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# Example

// before
public class VideoEncoder {
    public void encode(Video video) {
        System.out.println("Encoding video...");
        System.out.println("Done!\n");
    }
}

// after 
public interface VideoEncoder {
	void encode(Video video);
}

public class XVideoEncoder implements VideoEncoder {
    @Override
    public void encode(Video video) {
        System.out.println("Encoding video...");
        System.out.println("Done!\n");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • Click Refactor
  • Click Interface
  • Click Rename original class and use interface where possible
// before 
public class VideoProcessor {
    public void process(Video video) {
        var encoder = new VideoEncoder();
        encoder.encode(video);
    }
}

// after 
public class VideoProcessor {
    private VideoEncoder encoder;

    public VideoProcessor(VideoEncoder encoder) {
        this.encoder = encoder;
    }

    public void process(Video video) {
        encoder.encode(video);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// before
public class Main {
    public static void main(String[] args) {
        var video = new Video();
        video.setFileName("birthday.mp4");
        video.setTitle("Jennifer's birthday");
        video.setUser(new User("john@domain.com"));

        var processor = new VideoProcessor();
        processor.process(video);
    }
}

// after
public class Main {
    public static void main(String[] args) {
        var video = new Video();
        video.setFileName("birthday.mp4");
        video.setTitle("Jennifer's birthday");
        video.setUser(new User("john@domain.com"));

        var processor = new VideoProcessor(
            new XVideoEncoder(),
        );
        processor.process(video);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Bad Feature: Declare fields, static or private methods in interface

Why shouldn’t we declare fields, static or private methods in interfaces?

  • Fields, static and private methods are all about implementation
  • Interfaces are contracts and should not have any implementation.
  • Other classes should not know the implementation details of other classes

# Declare fields in interface

public interface TaxCalculator {
    // the intention is to avoid magic numbers 
    float minimumTax = 100; // final and static
    
    double calculateTax();
}

public class TaxCalculator2018 implements TaxCalculator {
    // should use the override key word
    @Override
    public double calculateTax() {     
        return TaxCalculator.minimumTax + taxableIncome * 0.3;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Declare static methods in interface

public interface TaxCalculator {   
    double calculateTax();
    
    // implementation details
    static double getTaxableIncome(double income, double expenses) {
        return income - expenses; 
    }
}


// better way: use an abstract class instead 
public interface TaxCalculator {   
    double calculateTax();
}

public abstract class AbstractTaxCalculator implements TaxCalculator {
    protected double getTaxableIncome(double income, double expenses) {
        return income - expenses; 
    }
}

// sub classes do not need to implement the interface again
public class TaxCalculator2018 extends AbstractTaxCalculator {
    // should use the override key word
    @Override
    public double calculateTax() {   
        int left = getTaxableIncome(100, 10);
        return TaxCalculator.minimumTax + taxableIncome * 0.3;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# Declare private methods in interface

public interface TaxCalculator { 
    // reason: we might end up with some repetitive logic, so can refactor our code,
    // and extract that redundent logic into a private method inside an interface (terrible) 
    private static double getTaxableIncome(double income, double expenses) {
        return income - expenses; 
    }
}
1
2
3
4
5
6
7

# Interfaces VS abstract classes

image-20211228165740341
  • Both are abstract concepts and we cannot instantiate them

  • Interfaces are contracts and should only have method declarations

  • Abstract classes are partially-implemented classes

    • We use them to share some common code across their derivates
  • The new features in Java allow writing code and logic in interfaces but this is a bad practice and should be avoided

  • Avoid abusing interfaces like this: (multiple inheritance)

image-20211228165848581

# Should we extract an interface from every class?

  • Blindly extracting interfaces doesn’t solve any problems nor is it considered a best practice
  • If you extract an interface from every single class, you’ll end up with an explosion of interfaces that don’t necessarily add any values
  • You should use interfaces in situations where you want to decouple a class from its dependencies so you can swap these dependencies
  • This allows building applications that are extensible and testable.

# Benefits of Interfaces

# Update Implementations for better performance

image-20211228170612151
  • Can easily replace the encoding algorithm with a faster algorithm or other third party services in the future
  • Decoupling these classes so one can supply new classes in the future with minimal or zero impact on the application
  • If we do not expect to replace classes with better ones, we should not extract interfaces

# Extend Applications with minimum impact

image-20211228172043288
  • Others can easily extend your applications with minimal impact
  • Instead of programming against a concrete implementation like a concrete template engine, you program against interfaces
  • Other people can create new template engines that implement your interfaces
  • So by using interfaces, you add extensibility points to your framework

# Test classes in isolation

image-20211228172332053
  • Decouple classes from dependencies and test this class in isolation

  • This is what we call unit testing

  • If you don't practice unit testing in your organization then extracting these interfaces doesn't really give you any benefits

# Reminder

  • So once again, don't just blindly write code and follow these so called best practices because someone told you to do so
  • For every piece of code you write you need to understand the problem you're trying to solve.
  • If you are not solving any problems, you are wasting your time and not adding any values
  • In fact, you are increasing the cost, because the more code you write the more bugs you're going to create, and the maintenance of your applications is going to be more costly
上次更新: 2021/12/31, 15:34:56
Inheritance
Exceptions

← Inheritance Exceptions→

最近更新
01
day01-Java基础语法
08-31
02
1
08-29
03
路线
08-01
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×