Java geek


Related reading:

JAVA programming ideas (1) Increase extensibility through dependency injection (2) How to program for interface (3) Remove the ugly if, self-registration strategy mode elegant meet the open and close principle (4) JAVA programming ideas (Builder mode) classical paradigm and factory mode how to choose? Java Programming Philosophy (5) Event Notification pattern decoupling Process (6) Event Notification pattern decoupling Process (1) Simple and thorough understanding of inner classes and static inner classes (2) Memory optimization – using Java references for caching JAVA foundation (3) ClassLoader implementation hot loading JAVA foundation (4) enumeration (enum) and constant definition, factory class use contrast JAVA foundation (5) functional interface – reuse, The sword of decoupling HikariPool source code (2) Design idea borrowed from JetCache source code (1) beginning JetCache source code (2) Top view people in the workplace (1) IT big factory survival rules


1. The purpose of extends and implements

Implements are intended for interface oriented programming.

The purpose of inheritance (extends) is to acquire capabilities.

Combination 1 can’t do it. Combination 2 and inheritance do it. So when do you use combination and when do you use inheritance?

2. Usage scenarios for inheritance and implementation

Before deciding when to use composition and when to use inheritance, take a look at the various scenarios that use inheritance and implementation.

2.1. Only the implementation class, subclass method signature and interface are consistent

2.2. The subclass method signature is inconsistent with the interface, so interface oriented programming can be followed

Example:

import java.io.Closeable;

/ * * *@ClassName CloseDemo
 * @Description
 * @AuthorSonorous leaf *@DateThis was the 2020/6/13 *@VersionThe nuggets: 1.0 * https://juejin.cn/user/3544481219739870 * * /
public class CloseDemo implements Closeable {

    public static void main(String[] args) {
        // Implement the Closeable interface, initialize in a try, and execute the close method automatically when the try ends
        try (CloseDemo closeDemo = newCloseDemo()) { closeDemo.doSomeThing(); }}public CloseDemo(a) {
        // Simulate resource acquisition
        System.out.println("get resource.");
    }

    @Override
    public void close(a) {
        // Simulate the release of resources
        System.out.println("release resource.");
    }

    public void doSomeThing(a) {
        System.out.println("do some thing."); }}Copy the code

Output:

get resource.
do some thing.
release resource.
Copy the code

This scenario uses a method that identifies the interface and automatically executes that interface in certain circumstances, while the principles of interface-oriented programming are still in effect, and this scenario does not involve using composition or inheritance.

2.3. There are abstract classes, subclass method signature and interface consistency

The abstract class adds a method, OPERATION4, which is not exposed but is used only by subclasses, with the exposed interface unchanged.

In the end, the method signature of the two subclasses is consistent with the interface, with no more and no less methods, following the principle of interface oriented programming.

This scenario lends itself to inheritance.

2.4. The method signature of subclass is inconsistent with the abstract class name, which cannot follow the principle of interface oriented programming

2.4.1. Example: Type judgments and strong casts due to inheritance

/ * * *@ClassName DefaultCacheMonitor
 * @DescriptionThis is a cache event monitoring class for hit ratio statistics *@AuthorSonorous leaf *@Date2020/6/10 declared *@VersionThe nuggets: 1.0 * https://juejin.cn/user/3544481219739870 * * /
public class DefaultCacheMonitor {

    // The method parameters are interface oriented, but are internally reinforced according to the instance type, and are not visible from the method signature. Only the writer knows this, using inherited counterexamples.
    public synchronized void afterOperation(CacheEvent event) {
        if (event instanceof CacheGetEvent) {
            CacheGetEvent e = (CacheGetEvent) event;
            afterGet(e.getMillis(), e.getKey(), e.getResult());
        } else if (event instanceof CachePutEvent) {
            CachePutEvent e = (CachePutEvent) event;
            afterPut(e.getMillis(), e.getKey(), e.getValue(), e.getResult());
        } else if (event instanceofCacheLoadEvent) { CacheLoadEvent e = (CacheLoadEvent) event; afterLoad(e.getMillis(), e.getKey(), e.getLoadedValue(), e.isSuccess()); }}private void afterGet(long millis, Object key, CacheGetResult result) {
        // do some thing
    }

    private void afterPut(long millis, Object key, Object value, CacheResult result) {
        // do some thing
    }

    private void afterLoad(long millis, Object key, Object loadedValue, boolean success) {
        // do some thing}}Copy the code

2.4.2. Optimize inappropriate inheritance relationships into usage combinations

Following the above example, we optimize inheritance to composition:

1. The difference part of different events is abstracted as DetailInfo, and the different DetailInfo is recorded by generic variables, and inheritance is changed to combination.

2. Define the CacheEventType enumeration class to display differentiated event types.

3. Define different Detail classes according to different event types.

In this way, the handling of different event types is visible, eliminating the need for type strong-casting. Example code is as follows:

2.4.2.1. CacheEvent. Java

public class CacheEvent<T> {

    private CacheEventType cacheEventType;

    private Cache cache;

    private T detailInfo;

    public CacheEvent(Cache cache, CacheEventType cacheEventType, T detailInfo) {
        this.cache = cache;
        this.cacheEventType = cacheEventType;
        this.detailInfo = detailInfo;
    }

    public Cache getCache(a) {
        return cache;
    }

    public CacheEventType getCacheEventType(a) {
        return cacheEventType;
    }

    public T getDetailInfo(a) {
        returndetailInfo; }}Copy the code

2.4.2.2. CacheEventType. Java

public enum CacheEventType {
    GetEvent, LoadEvent, PutEvent;
}
Copy the code

2.4.2.3. GetEventDetail. Java

public class GetEventDetail {
    private long millis;
    private Object key;
    private CacheGetResult result;

    public GetEventDetail(long millis, Object key, CacheGetResult result) {
        this.millis = millis;
        this.key = key;
        this.result = result;
    }

    public long getMillis(a) {
        return millis;
    }

    public Object getKey(a) {
        return key;
    }

    public CacheGetResult getResult(a) {
        return result;
    }

    @Override
    public String toString(a) {
        StringBuilder builder = new StringBuilder();
        builder.append("GetEventDetail {")
                .append("millis=").append(millis)
                .append(", key=").append(key)
                .append("}");
        returnbuilder.toString(); }}Copy the code

2.4.2.4. LoadEventDetail. Java

public class LoadEventDetail {
    private final long millis;
    private final Object key;
    private final Object loadedValue;
    private final boolean success;

    public LoadEventDetail(long millis, Object key, Object loadedValue, boolean success) {
        this.millis = millis;
        this.key = key;
        this.loadedValue = loadedValue;
        this.success = success;
    }

    public long getMillis(a) {
        return millis;
    }

    public Object getKey(a) {
        return key;
    }

    public Object getLoadedValue(a) {
        return loadedValue;
    }

    public boolean isSuccess(a) {
        returnsuccess; }}Copy the code

2.4.2.5. PutEventDetail. Java

public class PutEventDetail {
    private long millis;
    private Object key;
    private Object value;
    private CacheResult result;

    public PutEventDetail(long millis, Object key, Object value, CacheResult result) {
        this.millis = millis;
        this.key = key;
        this.value = value;
        this.result = result;
    }

    public long getMillis(a) {
        return millis;
    }

    public Object getKey(a) {
        return key;
    }

    public CacheResult getResult(a) {
        return result;
    }

    public Object getValue(a) {
        returnvalue; }}Copy the code

2.4.2.6. DefaultCacheMonitor. Java

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

public class DefaultCacheMonitor {
    // Store consumers consuming different events and avoid awkward if judgments
    private Map<CacheEventType, Consumer> consumerMap = new ConcurrentHashMap<>();

    public DefaultCacheMonitor(a) {
        registerConsumer();
    }

    public synchronized void afterOperation(CacheEvent event) {
        // Get the event consumer based on the event type
        Consumer consumer = consumerMap.get(event.getCacheEventType());
        if(consumer ! =null) { consumer.accept(event.getDetailInfo()); }}// Register different event consumers according to the event type, avoid using if
    private void registerConsumer(a) {
        // Function interfaces use generics to avoid strong class examples
        consumerMap.put(CacheEventType.GetEvent, new Consumer<GetEventDetail>() {
            @Override
            public void accept(GetEventDetail o) {
                System.out.println("consume GetEventDetail"); System.out.println(o.toString()); }}); consumerMap.put(CacheEventType.LoadEvent,new Consumer<LoadEventDetail>() {
            @Override
            public void accept(LoadEventDetail o) {
                System.out.println("consume LoadEventDetail"); System.out.println(o.toString()); }}); consumerMap.put(CacheEventType.PutEvent,new Consumer<PutEventDetail>() {
            @Override
            public void accept(PutEventDetail o) {
                System.out.println("consume PutEventDetail"); System.out.println(o.toString()); }}); }}Copy the code

2.4.2.7. EntryDemo. Java

public class EntryDemo {
    public static void main(String[] args) {
        DefaultCacheMonitor monitor = new DefaultCacheMonitor();
        CacheEvent<GetEventDetail> cacheEvent = new CacheEvent<>(new Cache(),
                CacheEventType.GetEvent,
                new GetEventDetail(1001."apple".newCacheGetResult())); monitor.afterOperation(cacheEvent); }}Copy the code

Demo output:

consume GetEventDetail
GetEventDetail {millis=1001, key=apple }
Copy the code

3. Summary

At this point, we can sort out which scenarios are suitable for composition and which scenarios are suitable for inheritance.

1. Follow the principle of interface oriented programming when using inheritance, and consider using composition if you break this principle.


Inheritance usually occurs in abstract classes; if not, composition is preferred.


3. Using generic, functional interfaces, strategic patterns can optimize bad usage inheritance code into usage combinations.

end.


<– Read the mark, left like!