* Why Is Inheritance Harmful ?)

Source: Internet
Author: User
ArticleDirectory
    • Summary
By converting a base class into an interface, you can improve your Code

Translated by Allen Holub: shiningray @ Nirvana Studio

Summary

Most Outstanding designers avoid inheritance (ExtendsThe relationship described), just like avoiding the plague. Your code 80% should be completely written in interface mode, rather than inheriting the specific base class. In fact, Gang of Four's book on design patterns (hereinafter referred to as gof) is largely about how to transform class inheritance into interface implementation. This article describes why designers have such an odd creed. (2,300 words;August 1, 2003)

Translation: Someone has already translated this article. At that time, I started to translate it without specific understanding. If another translator saw this article, he hoped it would not be copied by me.

ExtendsKeywords are harmful. It may not only be at the level of Charles Mason, but also to the degree that should be avoided as long as possible. In gof, the class inheritance (Extends) How to convert to interface implementation (Implements ).

Most of the code of a good designer is written based on the interface, rather than the specific base class. This article will explain why designers have such an odd hobby and introduce some interface-based programming basics.

Interface vs class

I attended a Java User Group meeting, which happened to be a special speaker by James Gosling (inventor of Java. In the unforgettable Q & A Conversation (question), a person asked him: "If you can repeat Java, what changes will you make ?" "I will remove the class," he replied. As laughter gradually disappears, he explains that the real problem is not the nature of the class, but the inheritance of the class (ExtendsLink ). Interface implementation (ImplementsLink) is perfect. If possible, you should avoid class inheritance.

Loss of Elasticity

Why should you avoid class inheritance? The first problem is that the explicit use of the name of a specific class will put you in a specific implementation, making it very difficult for future changes.

At present, the core of agile development methodology is the synchronization of design and development. You are fully describingProgramBefore that, I began to write code. This technology completely violates the traditional philosophy-the design should be completed before programming-but many successful projects have been proven. With this method, you can develop high-quality code more quickly (and effectively at the same time) than traditional pipeline jobs ). However, the core of parallel development is the concept of elasticity. You must write your code in this way, so that you can add new requirements to the existing code as painless as possible.

You only need to implementIndeedThe required features, instead of implementing thosePossibleBut a method that can adapt to changes is required. If you do not have this elasticity, parallel development will obviously not work.

Interface Programming is the core of this elastic interface. To understand why, let's see what will happen if you don't use the interface. Consider the following code:

F ()
{Shortlist list = new shortlist ();
//...
G (list );
}

G (sort list List)
{
List. Add (...);
G2 (list)
}

Now we assume that an urgent new requirement needs to be searched more quickly and exposed.ShortlistYou cannot meet the requirements. You need to replace itHashset. In the existing code, because you must modifyF ()AndG ()(It usesShortlistAs a parameter), so the changes are not limited to one, and everything is passed to the listG ().

Change the code to the following:

F ()
{Collection list = new collections list ();
//...
G (list );
}

G (collection list)
{
List. Add (...);
G2 (list)
}

Now we want to change the linked list to a hash table.New shortlist ()ChangeNew hashset (). That's done. You do not need to change other places.

In another example, compare the Code:

F ()
{Collection C = new hashset ();
//...
G (c );
}

G (collection C)
{
For (iterator I = C. iterator (); I. hasnext ();)
Do_something_with (I. Next ());
}

And:

F2 ()
{Collection C = new hashset ();
//...
G2 (C. iterator ());
}

G2 (iterator I)
{While (I. hasnext ();)
Do_something_with (I. Next ());
}

G2 ()Method traversal nowCollectionAndMapTo obtain the key and value. In fact, you can write an iterator that constantly generates data instead of traversing a set. You can write many different iterations. For example, you can continuously provide information from the test platform or in a file. This is the most important elasticity here.

Coupling

A more critical issue about class inheritance is coupling-the dependency of an unexpected part in the program on another part. Global variables provide a classic example to illustrate why strong coupling causes many problems. For example, if you change the global variable type, all functions that use this variable (that is, for this variableCouplingIn this way, all such code must be checked, modified, and re-tested. In addition, all functions that use this variable will also produce coupling through this variable. That is, a function may incorrectly change this variable, resulting in the behavior of other functions, if the variable value is changed at some special time. This problem is particularly prominent in multithreaded programs.

As a designer, you should strive to achieve the lowest Coupling Degree. Of course, you cannot completely eliminate coupling, because one class object calls another object is a form of loose coupling. You cannot write a program without any coupling. In this case, you can minimize coupling by absolutely following the OO principle (the most important thing is that the implementation details of an object should be hidden for the objects using it. For example, the instance variable of an object (a very large number of member fields) should always bePrivate. There are no exceptions (you can occasionally use the protected method very effectively, but the protected instance variable is quite annoying, you must never use the set/get functions-they are just a slightly complicated method to make fields public. (Although it is reasonable to return the accessed function of the processed object instead of a basic type value, if the class of the returned object is a key abstraction in the design .)

I am not playing tricks here. I found a dependency between the strict oo approach, fast code development, and simple code maintenance. Whenever I violate a core oo principle, such as hiding implementation details, I can only modify the code (usually because the Code cannot be debugged ). I don't have time to rewrite the program, so I can only follow these rules. I am concerned with the actual content-I am not interested in designing for design purposes.

Brittle base Problems

Now, we apply the concept of coupling to inheritance. In an implementation-inheritance system, the derived class has very tight coupling with the base class, and this closed connection is not welcome. Designers therefore give this behavior a nickname-"brittle basis issues ". The base class is considered very fragile, because you can modify a base class in a seemingly safe way, but this new behavior is inherited by the derived class, it may cause an error in running the derived class. You cannot simply check the methods of the base class to determine whether your changes to the base class are safe. You must also view (and test) All the derived classes. In addition, you must check all the code that uses both the base class and the derived class object, because the code may be damaged by new behaviors. A small change to the key base class will make the entire program unable to run.

Let's test the coupling between the brittle base class and the base class. The following class extends the JavaArraylistClass to simulate stack behavior:

Class Stack extends arraylist
{Private int stack_pointer = 0;

Public void push (Object article)
{Add (stack_pointer ++, article );
}

Public object POP ()
{Return remove (-- stack_pointer );
}

Public void push_many (object [] Articles)
{For (INT I = 0; I <articles. length; ++ I)
Push (Articles [I]);
}
}

Even a simple class like this has problems. Think about using inheritance directlyArraylistOfClear ()Method to play all elements out of the stack. What will happen:

Stack a_stack = new stack ();
A_stack.push ("1 ");
A_stack.push ("2 ");
A_stack.clear ();

This code can be compiled successfully, but the base class does not know any information about the stack pointer,StackThe object is in an uncertain state. Next, callPush ()The new entries will be placed in two types of indexes (Treasure pointerStack_pointerThe current value), so the stack looks like there are three elements-but the two are already garbage collected. (In the Java class libraryStackClass, so do not use)

For method inheritance that is not needed, one solution isStackTo override allArraylistMay modify the array status method, so that the overwriting function can correctly handle the stack pointer or throw an exception. (Removerange ()Method is a good candidate for throwing an exception .)

This method has two disadvantages. First, if you overwrite everything, the base class actually becomes an interface instead of a class. If you do not use any inherited methods, class inheritance is meaningless. Second, it is more important that you do not want a stack to support allArraylist. For example, the nastyRemoverange ()The method is useless. The only reasonable way to implement a useless method is to let him throw an exception so that he cannot be called. This method turns a compile-time error into a runtime error. This is not good. If the method is not declared, the compiler will directly throw a "method not found" error. If this method exists but it throws an exception, you will not find the error until the program runs.

A better solution is to encapsulate a data structure without using inheritance. Below isStackImproved versions:

Class Stack
{Private int stack_pointer = 0;
Private arraylist the_data = new arraylist ();

Public void push (Object article)
{The_data.add (stack_pointer ++, article );
}

Public object POP ()
{Return the_data.remove (-- stack_pointer );
}

Public void push_many (object [] Articles)
{For (INT I = 0; I <O. length; ++ I)
Push (Articles [I]);
}
}

So far, it is not bad, but we still need to consider the problem of the brittle base class. Let's assume that you want to createStackYou can track the maximum number of stacks that have occurred after running for a period of time. One possible implementation is as follows:

Class monitorable_stack extends Stack
{
Private int high_water_mark = 0;
Private int current_size;

Public void push (Object article)
{If (++ current_size> high_water_mark)
High_water_mark = current_size;
Super. Push (Article );
}

Public object POP ()
{-- Current_size;
Return super. Pop ();
}

Public int maximum_size_so_far ()
{Return high_water_mark;
}
}

The new class runs well, at least for the moment. Unfortunately, the code is exposed.Push_many ()By callingPush ()To complete its tasks. First, this detail does not seem a bad choice. It simplifies the code and you can getPush ()Version, even whenMonitorable_stackIs throughStackType references can also be completed, so,High_water_markIs correct.

One day, someone may run a test tool and find outStackIt is not fast enough, and it is frequently used. You can override an unusedArraylistOfStack, Thereby improvingStackPerformance. The latest version is as follows:

Class Stack
{Private int stack_pointer =-1;
Private object [] stack = new object [1000];

Public void push (Object article)
{Assert stack_pointer <stack. length;

Stack [++ stack_pointer] = article;
}

Public object POP ()
{Assert stack_pointer> = 0;

Return stack [stack_pointer --];
}

Public void push_many (object [] Articles)
{Assert (stack_pointer + articles. Length) <stack. length;

System. arraycopy (articles, 0, stack, stack_pointer + 1,
Articles. Length );
Stack_pointer + = articles. length;
}
}

Note:Push_many ()No longer repeated callsPush ()But uses block transfer. The new version of stack runs well. In fact, it is better than the original version.Good. Unfortunately, the derived classMonitorable_stackIt can no longer work normally, because if you callPush_many ()So he cannot correctly track the usage of the stack (derived classPush ()The version is no longer inheritedPush_many ()So he will not updateHigh_water_mark). NowStackIs a brittle base class. As shown above, in fact, it is impossible to eliminate such problems simply by being careful.

It is worth noting that if you use interface inheritance, this problem will not occur, because it will not inherit any functions and will not have adverse effects. IfStackIs an interfaceSimple_stackAndMonitorable_stackThen the code will be stronger.

In Table 0.1, I provide an interface-based solution. This method has the same elasticity as the class-inherited method: You canStackAbstract to write your code without worrying about the type of stack you need to deal. Because both implementations must provide all the methods in the public interface, it is difficult to have problems. I also have one and only one benefit from writing the same base class code, because I use encapsulation instead of derivation. From the negative, I have to access the default implementation through a small accessor method in the encapsulation class. (For exampleMonitorable_stack.push (...)(41 rows) must be calledSimple_stack.) Programmers always complain about writing such a line of code, but simply writing such an extra line can eliminate the huge potential bugs.

Table 0.1. Use interfaces to remove brittle base classes

1 | import java. util .*;
2 |
3 | interface Stack
4 | {
5 | void push (Object O );
6 | object POP ();
7 | void push_many (object [] source );
8 |}
9 |
10 | class simple_stack implements Stack
11 | {private int stack_pointer =-1;
12 | private object [] stack = new object [1, 1000];
13 |
14 | public void push (Object O)
15 | {assert stack_pointer <stack. length;
16 |
17 | stack [++ stack_pointer] = O;
18 |}
19 |
20 | public object POP ()
21 | {assert stack_pointer> = 0;
22 |
23 | return stack [stack_pointer --];
24 |}
25 |
26 | public void push_many (object [] source)
27 | {assert (stack_pointer + source. Length) <stack. length;
28 |
29 | system. arraycopy (source, 0, stack, stack_pointer + 1, source. Length );
30 | stack_pointer + = source. length;
31 |}
32 |}
33 |
34 |
35 | class monitorable_stack implements Stack
36 | {
37 | private int high_water_mark = 0;
38 | private int current_size;
39 | simple_stack stack = new simple_stack ();
40 |
41 | public void push (Object O)
42 | {If (++ current_size> high_water_mark)
43 | high_water_mark = current_size;
44 | stack. Push (O );
45 |}
46 |
47 | public object POP ()
48 | {-- current_size;
49 | return stack. Pop ();
50 |}
51 |
52 | public void push_many (object [] source)
53 | {
54 | if (current_size + source. length> high_water_mark)
55 | high_water_mark = current_size + source. length;
56 |
57 | stack. push_source (source );
58 |}
59 |
60 | public int maximum_size ()
61 | {return high_water_mark;
62 |}
63 |}
64 |

Framework (frameworks)

If framework-based programming is not mentioned in the discussion of brittle base classes, it will not be a complete discussion. Frameworks such as MFC (Microsoft basic library) have become a popular method for creating class libraries. Although he is leaving the MFC, the structure of the MFC has been deeply rooted in numerous Microsoft workshops-programmers here think that Microsoft's approach is the best.

A framework-based system generally starts with a library of semi-finished classes. These semi-finished classes do not complete everything, but rely on the derived classes to provide unfinished functions. A typical example in Java isComponentOfPaint ()Method, which is actually a placeholder, and the derived class must provide a real version.

You can, but a class framework is extremely vulnerable to relying on derived-based custom settings. The base class is also very fragile. When I program using MFC, Every time Microsoft releases a new version of MFC, I have to rewrite my own application. The code is often compiled, but it cannot work correctly next because the methods of some base classes have changed.

All Java packages can be used out of box and run well. You do not need to extend anything to allow them to execute functions. The out-of-the-box structure is better than the derived framework. It is easier to maintain and use, and even if the class provided by Sun changes its implementation, it will not put your code in danger.

Summary of brittle base classes

In general, it is best to avoid inheriting a specific base class andExtendsLink, while using the interface andImplementsLink. In my experience, at least 80% of the Code should be completely written using interfaces. For example, I never referenceHashmap; I usually useMapAPI reference. (The "interface" here is generalized. OneInputstreamIt is also a valid interface. You can see how to use it, although it is described as an abstract class in Java .)

The more abstraction you add, the better elasticity. In today's business environment, the requirements for program development are constantly changing, and elasticity is essential. In addition, most agile development methods (such as The Crystal Method and eXtreme Programming) do not work unless the code is first abstracted.

If you carefully study the Gang of Four mode, you will find that many of them provide various methods to eliminate class inheritance, focusing on using interfaces, this is also a common feature of most models. We need to understand an important fact at the beginning: patterns are discovered, not invented. When you read the code that is well written and easy to maintain and can be well performed, the pattern will naturally emerge. This tells us that so many excellent codes will avoid class inheritance in various ways.

This article is excerpted from my upcoming book 《Holub on patterns: Learning design patterns by looking at codeWill be passed this fallApress(Www.apress.com.

About the author

Allen HolubHe has been engaged in the computer industry since 1979. He is currently a consultant who provides administrative staff with advice, training, design, and programming services to help enterprises no longer need to waste money on software. He has written 8 books, includingTaming Java threads (Apress, 2000) andCompiler Design in C (Pearson higher education, 1990) and teaches at UC Berkeley. Please go to his website (Http://www.holub.com.

Resources

  • A good, though academic, treatment of fragile base classes by Leonid mikhajlov and Emil sekerinski:
    Http://www.cas.mcmaster.ca /~ Emil/publications/fragile/ecoop98.pdf
  • See all of Allen Holub'sJava toolboxColumns:
    Http://www.javaworld.com/columns/jw-toolbox-index.shtml
  • View David Geary'sJava Design PatternsColumns:
    Http://www.javaworld.com/columns/jw-java-design-patterns-index.shtml
  • The Gang of Four book is the seminal work on design patterns:Design patterns,Eric gamma, Richard Helm, Ralph Johnson, and John vlissides (Addison-Wesley Publishing Co., 1995; ISBN: 0201633612 ):
    Http://www.amazon.com/exec/obidos/ASIN/0201633612/javaworld
  • BrowseDesign PatternsSectionJavaworld'S topical index:
    Http://www.javaworld.com/channel_content/jw-patterns-index.shtml
  • BrowseObject-oriented design and programmingSectionJavaworld'Topical index:
    Http://www.javaworld.com/channel_content/jw-oop-index.shtml
  • Visit the javaworld Forum:
    Http://www.javaworld.com/javaforums/ubbthreads.php? Cat = & C = 2
  • Sign upJavaworld'S free weekly newsletters:
    Http://www.javaworld.com/subscribe

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.