Composition over inheritance: Difference between revisions

Content deleted Content added
 
(43 intermediate revisions by 31 users not shown)
Line 1:
{{Short description|Software design pattern}}
{{Confusing|date=October 2015}}
[[File:UML diagram of composition over inheritance.svg|thumb|right|444px|This diagram shows how the fly and sound behavior of an animal can be designed in a flexible way by using the composition over inheritance design principle.<ref name="FHDPs" />]]
'''Composition over inheritance''' (or '''composite reuse principle''') in [[object-oriented programming]] (OOP) is the principle that classes should achievefavor [[Polymorphism (computer science)|polymorphic]] behavior and [[code reuse]] by their [[object composition|composition]] (by containing instances of other classes that implement the desired functionality) rather thanover [[Inheritance (computer science)|inheritance]] from a base or parent class.<ref>{{cite book
| url = https://books.google.com/books?id=4pjbgVHzomsC&q=composite+reuse+principle&pg=PA17
| title = Java Design - Objects, UML, and Process: 1.1.5 Composite Reuse Principle (CRP)
Line 11 ⟶ 10:
| isbn = 9780201750447
| accessdate = 2012-05-29
}}</ref> ThisIdeally isall anreuse often-statedcan principlebe ofachieved OOPby assembling existing components, suchbut in practice inheritance is often needed to make new ones. Therefore inheritance and object composition typically work hand-in-hand, as discussed in the influential book ''[[Design Patterns]]'' (1994).<ref>{{Cite book | isbn = 0-201-63361-2 | title = [[Design Patterns|Design Patterns: Elements of Reusable Object-Oriented Software]] | last1 = Gamma | first1 = Erich | authorlink1 = Erich Gamma | last2 = Helm | first2 = Richard | last3 = Johnson | first3 = Ralph | authorlink3 = Ralph Johnson (computer scientist) | last4 = Vlissides | first4 = John | authorlink4 = John Vlissides | year = 1994 | publisher = [[Addison-Wesley]] | page = [https://archive.org/details/designpatternsel00gamm/page/20 20] | oclc = 31171684 }}
</ref>
 
==Basics==
An implementation of composition over inheritance typically begins with the creation of various [[Interface (computing)#In object-oriented languages|interfaces]] representing the behaviors that the system must exhibit. Interfaces enablecan facilitate [[Polymorphism (computer science)|polymorphic]] behavior. Classes implementing the identified interfaces are built and added to [[business ___domain]] classes as needed. Thus, system behaviors are realized without inheritance.
 
In fact, business ___domain classes may all be base classes without any inheritance at all. Alternative implementation of system behaviors is accomplished by providing another class that implements the desired behavior interface. A class that contains a reference to an interface can support implementations of the interface—a choice that can be delayed until [[Runtime (program lifecycle phase)|runtime]].
 
==Example==
Line 74 ⟶ 73:
* class {{code|Trap}} - which is {{code|Solid}}, but neither {{code|Visible}} nor {{code|Movable}}
 
Note that multiple inheritance is dangerous if not implemented carefully, asbecause it can lead to the [[Multiple inheritance#The diamond problem|diamond problem]]. One solution to avoid this is to create classes such as {{code|VisibleAndSolid}}, {{code|VisibleAndMovable}}, {{code|VisibleAndSolidAndMovable}}, etc. for every needed combination,; thoughhowever, this leads to a large amount of repetitive code. C++ solvesuses [[virtual inheritance]] to solve the diamond problem of multiple inheritance by allowing [[virtual inheritance]].
 
===Composition and interfaces===
The C++ examples in this section demonstrate the principle of using composition and interfaces to achieve code reuse and polymorphism. Due to the C++ language not having a dedicated keyword to declare interfaces, the following C++ example uses "inheritance from a pure [[abstract base class"]]. For most purposes, this is functionally equivalent to the interfaces provided in other languages, such as Java<ref name=Bloch>{{cite book | title= "Effective Java: Programming Language Guide" |last=Bloch| first=Joshua| publisher=Addison-Wesley | edition=third | isbn=978-0134685991| year=2018}}</ref>{{rp|87}} and C#.<ref name=Price>{{cite book |last=Price | first=Mark J. |title=C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development: Build Applications with C#, .NET Core, Entity Framework Core, ASP.NET Core, and ML.NET Using Visual Studio Code | date=2022 | publisher= Packt |isbn= 978-1-098-12195-2}}</ref>{{rp|144}}
 
Introduce an abstract class named {{code|VisibilityDelegate}}, with the subclasses {{code|NotVisible}} and {{code|Visible}}, which provides a means of drawing an object:
Line 212 ⟶ 211:
 
==Benefits==
To favor composition over inheritance is a design principle that gives the design higher flexibility. It is more natural to build business-___domain classes out of various components than trying to find commonality between them and creating a family tree. For example, an accelerator pedal and a steering wheel share very few common [[Trait (computer programming)|traits]], yet both are vital components in a car. What they can do and how they can be used to benefit the car isare easily defined. Composition also provides a more stable business ___domain in the long term as it is less prone to the quirks of the family members. In other words, it is better to compose what an object can do (''[[HAShas-Aa]]'') than extend what it is (''[[ISis-Aa]]'').<ref name="FHDPs">{{cite book
| last1 = Freeman
| first1 = Eric
Line 233 ⟶ 232:
Composition relation is more flexible as it may be changed on runtime, while sub-typing relations are static and need recompilation in many languages.
 
Some languages, notably [[Go (programming language)|Go]] and Rust use type composition exclusively.<ref>{{cite web |url=https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html |title=Less is exponentially more |last1=Pike |first1=Rob |date=2012-06-25 |accessdate=2016-10-01 }}</ref> and [[Rust (programming language)|Rust]],<ref>{{Cite web |title=Characteristics of Object-Oriented Languages - The Rust Programming Language |url=https://doc.rust-lang.org/stable/book/ch17-01-what-is-oo.html#inheritance-as-a-type-system-and-as-code-sharing |access-date=2022-10-10 |website=doc.rust-lang.org}}</ref> use type composition exclusively.
 
==Drawbacks==
One common drawback of using composition instead of inheritance is that methods being provided by individual components may have to be implemented in the derived type, even if they are only [[Forwarding (object-oriented programming)|forwarding methods]] ''(this is true in most programming languages, but not all; see [[#Avoiding{{section drawbackslink|#Avoiding drawbacks]]}}).)'' In contrast, inheritance does not require all of the base class's methods to be re-implemented within the derived class. Rather, the derived class only needs to implement (override) the methods having different behavior than the base class methods. This can require significantly less programming effort if the base class contains many methods providing default behavior and only a few of them need to be overridden within the derived class.
 
For example, in the C# code below, the variables and methods of the {{code|Employee}} base class are inherited by the {{code|HourlyEmployee}} and {{code|SalariedEmployee}} derived subclasses. Only the {{code|Pay()}} method needs to be implemented (specialized) by each derived subclass. The other methods are implemented by the base class itself, and are shared by all of its derived subclasses; they do not need to be re-implemented (overridden) or even mentioned in the subclass definitions.
 
[[File:UML class Employee.svg|UML class Employee.svg]]
 
<syntaxhighlight lang="csharp">
Line 278 ⟶ 279:
 
===Avoiding drawbacks===
This drawback can be avoided by using [[Traits (computer science)|traits]], [[mixin]]s, ''(type)'' [[embedding]], or [[Protocol (object-oriented programming)|protocol]] extensions.
 
Some languages provide specific means to mitigate this:
 
* [[C Sharp (programming language)|C#]] provides default interface methods since version 8.0 version which allows to define body to interface member.<ref>{{cite web | url=https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#default-interface-methods | title=What's new in C# 8.0 | website=Microsoft Docs | publisher=Microsoft | access-date=2019-02-20}}</ref><ref name=Price/>{{rp|28–29}}<ref name=Skeet>{{cite book |last=Skeet|first=Jon|title= C# in Depth |date=23 March 2019 |publisher= Manning |isbn= 978-1617294532}}</ref>{{rp|38}}<ref name=Albahari>{{cite book |last=Albahari |first=Joseph |title= C# 10 in a Nutshell |date=2022 |publisher= O'Reilly |isbn= 978-1-098-12195-2}}</ref>{{rp|466–468}}
* [[D (programming language)|D]] provides an explicit "alias this" declaration within a type can forward into it every method and member of another contained type.<ref>{{cite web | url=https://dlang.org/spec/class.html#alias-this | title=Alias This | website=D Language Reference| access-date=2019-06-15}}</ref>
* [[Dart (programming language)|Dart]] provides mixins with default implementations that can be shared.
* [[Go (programming language)|Go]] type embedding avoids the need for forwarding methods.<ref>{{cite web | url=https://golang.org/doc/effective_go.html#embedding | title=''(Type)'' Embedding | website=The Go Programming Language Documentation | access-date=2019-05-10}}</ref>
* [[Java (programming language)|Java]] provides default interface methods since version 8.<ref name=Bloch/>{{rp|104}} Project Lombok<ref>https://projectlombok.org {{Bare URL inline|date=August 2024}}</ref> supports delegation using the {{code|@Delegate}} annotation on the field, instead of copying and maintaining the names and types of all the methods from the delegated field.<ref>{{cite web | url=https://projectlombok.org/features/experimental/Delegate.html | title=@Delegate | website=Project Lombok | access-date=2018-07-11}}</ref>
* [[Julia (programming language)|Julia]] macros can be used to generate forwarding methods. Several implementations exist such as [Lazy.jl<ref>{{cite web | url=https://github.com/MikeInnes/Lazy.jl | title=MikeInnes/Lazy.jl | website=[[GitHub]] }}</ref> and [TypedDelegation.jl.<ref>{{cite web | url=https://github.com/JeffreySarnoff/TypedDelegation.jl | title=JeffreySarnoff/TypedDelegation.jl | website=[[GitHub]] }}</ref><ref>{{cite web |title=Method forwarding macro |url=https://discourse.julialang.org/t/method-forwarding-macro/23355 |website=JuliaLang |access-date=18 August 2022 |language=en |date=20 April 2019}}</ref>
* [[Kotlin (programming language)|Kotlin]] includes the delegation pattern in the language syntax.<ref>{{cite web | url=https://kotlinlang.org/docs/reference/delegated-properties.html | title=Delegated Properties | website=Kotlin Reference | publisher=JetBrains | access-date=2018-07-11}}</ref>
* [[PHP]] supports [[Traits (computer science)|traits]], since PHP 5.4.<ref>{{cite web |title=PHP: Traits |url=https://www.php.net/manual/en/language.oop5.traits.php |website=www.php.net |access-date=23 February 2023}}</ref>
* [[Raku (programming language)|Raku]] provides a {{code|handles}} keyword to facilitate method forwarding.
* [[Raku (programming language)|Raku]] provides a {{code|handles}} trait to facilitate method forwarding.<ref>{{cite web |title=Type system |url=https://docs.raku.org/language/typesystem#index-entry-handles_trait-handles |website=docs.raku.org |access-date=18 August 2022}}</ref>
* [[Rust (programming language)|Rust]] provides traits with default implementations.
* [[Scala (programming language)|Scala]] (since version 3) provides an "export" clause to define aliases for selected members of an object.<ref>{{cite web | url=https://docs.scala-lang.org/scala3/reference/other-new-features/export.html | title=Export Clauses | website=Scala Documentation | access-date=2021-10-06}}</ref>
Line 297 ⟶ 299:
A 2013 study of 93 open source Java programs (of varying size) found that:
 
{{quoteblockquote|While there is not {{sic|huge}} opportunity to replace inheritance with composition (...), the opportunity is significant (median of 2% of uses [of inheritance] are only internal reuse, and a further 22% are only external or internal reuse).
 
Our results suggest there is no need for concern regarding abuse of inheritance (at least in open-source Java software), but they do highlight the question regarding use of composition versus inheritance. If there are significant costs associated with using inheritance when composition could be used, then our results suggest there is some cause for concern.|Tempero ''et al.'', "What programmers do with inheritance in Java"<ref>{{cite conference |last1=Tempero |first1=Ewan |first2=Hong Yul |last2=Yang |first3=James |last3=Noble |title=ECOOP 2013 – Object-Oriented Programming |chapter=What programmers do with inheritance in Java |conference=ECOOP 2013–Object-Oriented Programming |year=2013 |pages=577–601 |chapter-url=https://www.cs.auckland.ac.nz/~ewan/qualitas/studies/inheritance/TemperoYangNobleECOOP2013-pre.pdf |doi=10.1007/978-3-642-39038-8_24 |isbn=978-3-642-39038-8 |series=Lecture Notes in Computer Science |volume=7920}}</ref>}}
 
==See also==