Recent from talks
Contribute something
Nothing was collected or created yet.
Object-oriented programming
View on Wikipedia

Object-oriented programming (OOP) is a programming paradigm based on the object[1] – a software entity that encapsulates data and function(s). An OOP computer program consists of objects that interact with one another.[2][3] A programming language that provides OOP features is classified as an OOP language but as the set of features that contribute to OOP is contended, classifying a language as OOP and the degree to which it supports or is OOP, are debatable. As paradigms are not mutually exclusive, a language can be multi-paradigm; can be categorized as more than only OOP.
Sometimes, objects represent real-world things and processes in digital form.[4] For example, a graphics program may have objects such as circle, square, and menu. An online shopping system might have objects such as shopping cart, customer, and product. Niklaus Wirth said, "This paradigm [OOP] closely reflects the structure of systems in the real world and is therefore well suited to model complex systems with complex behavior".[5]
However, more often, objects represent abstract entities, like an open file or a unit converter. Not everyone agrees that OOP makes it easy to copy the real world exactly or that doing so is even necessary. Bob Martin suggests that because classes are software, their relationships don't match the real-world relationships they represent.[6] Bertrand Meyer argues that a program is not a model of the world but a model of some part of the world; "Reality is a cousin twice removed".[7] Steve Yegge noted that natural languages lack the OOP approach of naming a thing (object) before an action (method), as opposed to functional programming which does the reverse.[8] This can make an OOP solution more complex than one written via procedural programming.[9]
Notable languages with OOP support include Ada, ActionScript, C++, Common Lisp, C#, Dart, Eiffel, Fortran 2003, Haxe, Java,[10] JavaScript, Kotlin, Logo, MATLAB, Objective-C, Object Pascal, Perl, PHP, Python, R, Raku, Ruby, Scala, SIMSCRIPT, Simula, Smalltalk, Swift, Vala and Visual Basic (.NET).
History
[edit]The idea of "objects" in programming began with the artificial intelligence group at Massachusetts Institute of Technology (MIT) in the late 1950s and early 1960s. Here, "object" referred to LISP atoms with identified properties (attributes).[11][12] Another early example was Sketchpad created by Ivan Sutherland at MIT in 1960–1961. In the glossary of his technical report, Sutherland defined terms like "object" and "instance" (with the class concept covered by "master" or "definition"), albeit specialized to graphical interaction.[13] Later, in 1968, AED-0, MIT's version of the ALGOL programming language, connected data structures ("plexes") and procedures, prefiguring what were later termed "messages", "methods", and "member functions".[14][15] Topics such as data abstraction and modular programming were common points of discussion at this time.
Meanwhile, in Norway, Simula was developed during the years 1961–1967.[14] Simula introduced essential object-oriented ideas, such as classes, inheritance, and dynamic binding.[16] Simula was used mainly by researchers involved with physical modelling, like the movement of ships and their content through cargo ports.[16] Simula is generally accepted as being the first language with the primary features and framework of an object-oriented language.[17]
I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages (so messaging came at the very beginning – it took a while to see how to do messaging in a programming language efficiently enough to be useful).
Influenced by both MIT and Simula, Alan Kay began developing his own ideas in November 1966. He would go on to create Smalltalk, an influential OOP language. By 1967, Kay was already using the term "object-oriented programming" in conversation.[1] Although sometimes called the "father" of OOP,[18] Kay has said his ideas differ from how OOP is commonly understood, and has implied that the computer science establishment did not adopt his notion.[1] A 1976 MIT memo co-authored by Barbara Liskov lists Simula 67, CLU, and Alphard as object-oriented languages, but does not mention Smalltalk.[19]
In the 1970s, the first version of the Smalltalk programming language was developed at Xerox PARC by Alan Kay, Dan Ingalls and Adele Goldberg. Smalltalk-72 was notable for use of objects at the language level and its graphical development environment.[20] Smalltalk was a fully dynamic system, allowing users to create and modify classes as they worked.[21] Much of the theory of OOP was developed in the context of Smalltalk, for example multiple inheritance.[22]
In the late 1970s and 1980s, OOP rose to prominence. The Flavors object-oriented Lisp was developed starting 1979, introducing multiple inheritance and mixins.[23] In August 1981, Byte Magazine highlighted Smalltalk and OOP, introducing these ideas to a wide audience.[24] LOOPS, the object system for Interlisp-D, was influenced by Smalltalk and Flavors, and a paper about it was published in 1982.[25] In 1986, the first Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA) was attended by 1,000 people. This conference marked the start of efforts to consolidate Lisp object systems, eventually resulting in the Common Lisp Object System. In the 1980s, there were a few attempts to design processor architectures that included hardware support for objects in memory, but these were not successful. Examples include the Intel iAPX 432 and the Linn Smart Rekursiv.
In the mid-1980s, new object-oriented languages like Objective-C, C++, and Eiffel emerged. Objective-C was developed by Brad Cox, who had used Smalltalk at ITT Inc. Bjarne Stroustrup created C++ based on his experience using Simula for his PhD thesis.[20] Bertrand Meyer produced the first design of the Eiffel language in 1985, which focused on software quality using a design by contract approach.[26]
In the 1990s, OOP became the main way of programming, especially as more languages supported it. These included Visual FoxPro 3.0,[27][28] C++,[29] and Delphi[citation needed]. OOP became even more popular with the rise of graphical user interfaces, which used objects for buttons, menus and other elements. One well-known example is Apple's Cocoa framework, used on macOS and written in Objective-C. OOP toolkits also enhanced the popularity of event-driven programming.[citation needed]
At ETH Zürich, Niklaus Wirth and his colleagues created new approaches to OOP. Modula-2 (1978) and Oberon (1987), included a distinctive approach to object orientation, classes, and type checking across module boundaries. Inheritance is not obvious in Wirth's design since his nomenclature looks in the opposite direction: It is called type extension and the viewpoint is from the parent down to the inheritor.
Many programming languages that were initially developed before OOP was popular have been augmented with object-oriented features, including Ada, BASIC, Fortran, Pascal, and COBOL.
Features
[edit]
The OOP features provided by languages varies. Below are some common features of OOP languages.[30][31][32][33] Comparing OOP with other styles, like relational programming, is difficult because there isn't a clear, agreed-upon definition of OOP.[34]
Encapsulation
[edit]An object encapsulates fields and methods. A field (a.k.a. attribute or property) contains information (a.k.a. state) as a variable. A method (a.k.a. function or action) defines behavior via logic code. Encapsulation is about keeping related code together.
Information hiding
[edit]Information hiding is organizing code so that it is accessible only to the code that needs it; not to the rest of the codebase. The internal details of an object are hidden from the outside code, allowing for changing how an object works without affecting its interface and therefore other code. Hiding information helps prevent problems when changing the code.[35] Objects act as a barrier between their internal workings and external, consuming code. Consuming code can only interact with an object via its public members.
Some programming languages, like Java, provide information hiding via visibility key words (private and public).[36] Some languages don't provide a visibility feature, but developers might follow a convention such as starting a private member name with an underscore. Intermediate levels of access also exist, such as Java's protected keyword, (which allows access from the same class and its subclasses, but not objects of a different class), and the internal keyword in C#, Swift, and Kotlin, which restricts access to files within the same module.[37]
Supporters of information hiding and data abstraction say it makes code easier to reuse and intuitively represents real-world situations.[38][39] However, others argue that OOP does not enhance readability or modularity.[40][41] Eric S. Raymond has written that OOP languages tend to encourage thickly layered programs that destroy transparency.[42] Raymond compares this unfavourably to the approach taken with Unix and the C language.[42]
SOLID includes the open/closed principle, which says that classes and functions should be "open for extension, but closed for modification". Luca Cardelli has stated that OOP languages have "extremely poor modularity properties with respect to class extension and modification", and tend to be extremely complex.[40] The latter point is reiterated by Joe Armstrong, the principal inventor of Erlang, who is quoted as saying:[41]
The problem with object-oriented languages is they've got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
Leo Brodie says that information hiding can lead to duplicate code,[43] which goes against the don't repeat yourself rule of software development.[44]
Composition
[edit]Via object composition, an object can contain other objects. For example, an Employee object might contain an Address object, along with other information like name and position. Composition is a "has-a" relationships, like "an employee has an address".
Inheritance
[edit]Inheritance can be supported via the class or the prototype, which have differences but use similar terms like object and instance.
Class-based
[edit]In class-based programming, the most common type of OOP, an object is an instance of a class. The class defines the data (variables) and methods (logic). An object is created via the constructor. Elements of class may include:
- Class variable – belongs to the class itself; all objects of the class share one copy
- Instance variable – belongs to an object; every object has its own version of these variables
- Member variable – refers to both the class and instance variables of a class
- Class method – can only use class variables
- Instance method – belongs to an objects; can use both instance and class variables
Classes may inherit from other classes, creating a hierarchy of classes: a case of a subclass inheriting from a super-class. For example, an Employee class might inherit from a Person class which endows the Employee object with the variables from Person. The subclass may add variables and methods that do not affect the super-class. Most languages also allow the subclass to override super-class methods. Some languages support multiple inheritance, where a class can inherit from more than one class, and other languages similarly support mixins or traits. For example, a mixin called UnicodeConversionMixin might add a method unicode_to_ascii() to both a FileReader and a WebPageScraper class.
An abstract class cannot be directly instantiated as an object. It is only used as a super-class.
Other classes are utility classes which contain only class variables and methods and are not meant to be instantiated or subclassed.[45]
Prototype-based
[edit]Instead of providing a class concept, in prototype-based programming, an object is linked to another object, called its prototype or parent. In Self, an object may have multiple or no parents,[46] but in the most popular prototype-based language, JavaScript, an object has exactly one prototype link, up to the base object whose prototype is null.
A prototype acts as a model for new objects. For example, if you have an object fruit, you can make two objects apple and orange that share traits of the fruit prototype. Prototype-based languages also allow objects to have their own unique properties, so the apple object might have an attribute sugar_content, while the orange or fruit objects do not.
No inheritance
[edit]Some languages, like Go, don't support inheritance.[47] Instead, they encourage "composition over inheritance", where objects are built using smaller parts instead of parent-child relationships. For example, instead of inheriting from class Person, the Employee class could simply contain a Person object. This lets the Employee class control how much of Person it exposes to other parts of the program. Delegation is another language feature that can be used as an alternative to inheritance.
Programmers have different opinions on inheritance. Bjarne Stroustrup, author of C++, has stated that it is possible to do OOP without inheritance.[48] Rob Pike has criticized inheritance for creating complex hierarchies instead of simpler solutions.[49]
Inheritance and behavioral subtyping
[edit]People often think that if one class inherits from another, it means the subclass "is a" more specific version of the original class. This presumes the program semantics are that objects from the subclass can always replace objects from the original class without problems. This concept is known as behavioral subtyping, more specifically the Liskov substitution principle.
However, this is often not true, especially in programming languages that allow mutable objects, objects that change after they are created. In fact, subtype polymorphism as enforced by the type checker in OOP languages cannot guarantee behavioral subtyping in most if not all contexts. For example, the circle-ellipse problem is notoriously difficult to handle using OOP's concept of inheritance. Behavioral subtyping is undecidable in general, so it cannot be easily implemented by a compiler. Because of this, programmers must carefully design class hierarchies to avoid mistakes that the programming language itself cannot catch.
Dynamic dispatch
[edit]A method may be invoked via dynamic dispatch such that the method is selected at runtime instead of compile time. If the method choice depends on more than one type of object (such as other objects passed as parameters), it's called multiple dispatch.
Dynamic dispatch works together with inheritance: if an object doesn't have the requested method, it looks up to its parent class (delegation), and continues up the chain to find a matching method.
Message passing
[edit]Message passing is when the method name and its inputs are sent like a message to the object for it to act on.
Polymorphism
[edit]Polymorphism refers to subtyping or subtype polymorphism, where a function can work with a specific interface and thus manipulate entities of different classes in a uniform manner.[50]
For example, imagine a program has two shapes: a circle and a square. Both come from a common class called "Shape." Each shape has its own way of drawing itself. With subtype polymorphism, the program doesn't need to know the type of each shape, and can simply call the "Draw" method for each shape. The programming language runtime will ensure the correct version of the "Draw" method runs for each shape. Because the details of each shape are handled inside their own classes, this makes the code simpler and more organized, enabling strong separation of concerns.
Open recursion
[edit]An object's methods can access the object's data. Many programming languages use a special word, like this or self, to refer to the current object. In languages that support open recursion, a method in an object can call other methods in the same object, including itself, using this special word. This allows a method in one class to call another method defined later in a subclass, a feature known as late binding.
Design patterns
[edit]Design patterns are common solutions to problems in software design. Some design patterns are especially useful for OOP, and design patterns are typically introduced in an OOP context.
Object patterns
[edit]The following are notable software design patterns for OOP objects.[51]
- Function object: Class with one main method that acts like an anonymous function (in C++, the function operator,
operator()) - Immutable object: does not change state after creation
- First-class object: can be used without restriction
- Container object: contains other objects
- Factory object: creates other objects
- Metaobject: Used to create other objects (similar to a class, but an object)
- Prototype object: a specialized metaobject that creates new objects by copying itself
- Singleton object: only instance of its class for the lifetime of the program
- Filter object: receives a stream of data as its input and transforms it into the object's output
A common anti-pattern is the God object, an object that knows or does too much.
Gang of Four design patterns
[edit]Design Patterns: Elements of Reusable Object-Oriented Software is a famous book published in 1994 by four authors: Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. People often call them the "Gang of Four". The book talks about the strengths and weaknesses of OOP and explains 23 common ways to solve programming problems.
These solutions, called "design patterns," are grouped into three types:
- Creational patterns (5): Factory method pattern, Abstract factory pattern, Singleton pattern, Builder pattern, Prototype pattern
- Structural patterns (7): Adapter pattern, Bridge pattern, Composite pattern, Decorator pattern, Facade pattern, Flyweight pattern, Proxy pattern
- Behavioral patterns (11): Chain-of-responsibility pattern, Command pattern, Interpreter pattern, Iterator pattern, Mediator pattern, Memento pattern, Observer pattern, State pattern, Strategy pattern, Template method pattern, Visitor pattern
Object-orientation and databases
[edit]Both OOP and relational database management systems (RDBMSs) are widely used in software today. However, relational databases don't store objects directly, which creates a challenge when using them together. This issue is called object-relational impedance mismatch.
To solve this problem, developers use different methods, but none of them are perfect.[52] One of the most common solutions is object-relational mapping (ORM), which helps connect object-oriented programs to relational databases. Examples of ORM tools include Visual FoxPro, Java Data Objects, and Ruby on Rails ActiveRecord.
Some databases, called object databases, are designed to work with OOP. However, they have not been as popular or successful as relational databases.
Date and Darwen have proposed a theoretical foundation that uses OOP as a kind of customizable type system to support RDBMSs, but it forbids objects containing pointers to other objects.[53]
Responsibility- vs. data-driven design
[edit]In responsibility-driven design, classes are built around what they need to do and the information they share, in the form of a contract. This is different from data-driven design, where classes are built based on the data they need to store. According to Wirfs-Brock and Wilkerson, the originators of responsibility-driven design, responsibility-driven design is the better approach.[54]
SOLID and GRASP guidelines
[edit]SOLID is a set of five rules for designing good software, created by Michael Feathers:
- Single responsibility principle: A class should have only one reason to change.
- Open/closed principle: Software entities should be open for extension, but closed for modification.
- Liskov substitution principle: Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
- Interface segregation principle: Clients should not be forced to depend upon interfaces that they do not use.
- Dependency inversion principle: Depend upon abstractions, not concretes.
GRASP (General Responsibility Assignment Software Patterns) is another set of software design rules, created by Craig Larman, that helps developers assign responsibilities to different parts of a program:[55]
- Creator Principle: allows classes create objects they closely use.
- Information Expert Principle: assigns tasks to classes with the needed information.
- Low Coupling Principle: reduces class dependencies to improve flexibility and maintainability.
- High Cohesion Principle: designing classes with a single, focused responsibility.
- Controller Principle: assigns system operations to separate classes that manage flow and interactions.
- Polymorphism: allows different classes to be used through a common interface, promoting flexibility and reuse.
- Pure Fabrication Principle: create helper classes to improve design, boost cohesion, and reduce coupling.
Formal semantics
[edit]Researchers have tried to formally define the semantics of OOP. Inheritance presents difficulties, particularly with the interactions between open recursion and encapsulated state. Researchers have used recursive types and co-algebraic data types to incorporate essential features of OOP.[56] Abadi and Cardelli defined several extensions of System F<: that deal with mutable objects, allowing both subtype polymorphism and parametric polymorphism (generics), and were able to formally model many OOP concepts and constructs.[57] Although far from trivial, static analysis of object-oriented programming languages such as Java is a mature field,[58] with several commercial tools.[59]
Criticism
[edit]Some believe that OOP places too much focus on using objects rather than on algorithms and data structures.[60][61] For example, programmer Rob Pike pointed out that OOP can make programmers think more about type hierarchy than composition.[62] He has called OOP "the Roman numerals of computing".[63] Rich Hickey, creator of Clojure, described OOP as overly simplistic, especially when it comes to representing real-world things that change over time.[61] Alexander Stepanov said that OOP tries to fit everything into a single type, which can be limiting. He argued that sometimes we need multisorted algebras: families of interfaces that span multiple types, such as in generic programming. Stepanov also said that calling everything an "object" doesn't add much understanding.[60]
OOP was created to make code easier to reuse and maintain.[64] However, it was not designed to clearly show the flow of a program's instructions. That was left to the compiler. As computers began using more parallel processing and multiple threads, it became more important to understand and control how instructions flow. This is difficult to do with OOP.[65][66][67][68]
Many popular programming languages, like C++, Java, and Python, use OOP. In the past, OOP was widely accepted,[69] but recently, some programmers have criticized it and prefer functional programming instead.[70] A study by Potok et al. found no major difference in productivity between OOP and other methods.[71]
Paul Graham, a well-known computer scientist, believes big companies like OOP because it helps manage large teams of average programmers. He argues that OOP adds structure, making it harder for one person to make serious mistakes, but at the same time restrains smart programmers.[72] Eric S. Raymond, a Unix programmer and open-source software advocate, argues that OOP is not the best way to write programs.[42]
Richard Feldman says that, while OOP features helped some languages stay organized, their popularity comes from other reasons.[73] Lawrence Krubner argues that OOP doesn't offer special advantages compared to other styles, like functional programming, and can complicate coding.[74] Luca Cardelli says that OOP is slower and takes longer to compile than procedural programming.[40]
See also
[edit]- CADES
- Common Object Request Broker Architecture (CORBA)
- Comparison of programming languages (object-oriented programming)
- Component-based software engineering
- Distributed object
- Distributed Component Object Model
- Interface description language
- IDEF4
- Jeroo
- List of object-oriented programming languages
- Object association
- Object-oriented analysis and design
- Object-oriented modeling
- Object-oriented ontology
- UML
References
[edit]- ^ a b c d "Dr. Alan Kay on the Meaning of "Object-Oriented Programming"". 2003. Retrieved 11 February 2010.
- ^ Kindler, E.; Krivy, I. (2011). "Object-Oriented Simulation of systems with sophisticated control". International Journal of General Systems. 40 (3): 313–343. doi:10.1080/03081079.2010.539975.
- ^ Lewis, John; Loftus, William (2008). "1.6: Object-Oriented Programming". Java Software Solutions. Foundations of Programming Design (6th ed.). Pearson Education Inc. ISBN 978-0-321-53205-3.
- ^ Booch, Grady (1986). Software Engineering with Ada. Addison Wesley. p. 220. ISBN 978-0-8053-0608-8.
Perhaps the greatest strength of an object-oriented approach to development is that it offers a mechanism that captures a model of the real world.
- ^ Wirth, Niklaus (23 January 2006). "Good ideas, through the looking glass" (PDF). IEEE Computer. Cover Feature. 39 (1): 28–39. doi:10.1109/MC.2006.20. S2CID 6582369. Archived from the original (PDF) on 12 October 2016.
- ^ "Uncle Bob SOLID principles". YouTube. 2 August 2018.
- ^ Meyer 1997, p. 230.
- ^ Yegge, Steve (30 March 2006). "Execution in the Kingdom of Nouns". steve-yegge.blogspot.com. Retrieved 3 July 2010.
- ^ Boronczyk, Timothy (11 June 2009). "What's Wrong with OOP". zaemis.blogspot.com. Retrieved 3 July 2010.
- ^ Bloch 2018, pp. xi–xii, Foreword.
- ^ McCarthy, J.; Brayton, R.; Edwards, D.; Fox, P.; Hodes, L.; Luckham, D.; Maling, K.; Park, D.; Russell, S. (March 1969). "LISP I Programmers Manual" (PDF). Computation Center and Research Laboratory of Electronics. Boston, Massachusetts: Artificial Intelligence Group, M.I.T. Computation Center and Research Laboratory: 88f. Archived from the original (PDF) on 17 July 2010.
In the local M.I.T. patois, association lists [of atomic symbols] are also referred to as "property lists", and atomic symbols are sometimes called "objects".
- ^ McCarthy, John; Abrahams, Paul W.; Edwards, Daniel J.; Hart, Swapnil D.; Levin, Michael I. (1962). LISP 1.5 Programmer's Manual. MIT Press. p. 105. ISBN 978-0-262-13011-0.
Object – a synonym for atomic symbol
{{cite book}}: ISBN / Date incompatibility (help) - ^ Ivan E. Sutherland (May 1963). Sketchpad: a man-machine graphical communication system. AFIPS '63 (Spring): Proceedings of the May 21–23, 1963 Spring Joint Computer Conference. AFIPS Press. pp. 329–346. doi:10.1145/1461551.1461591.
- ^ a b Nygaard, Kristen; Dahl, Ole-Johan (1 August 1978). "The development of the SIMULA languages". ACM SIGPLAN Notices. 13 (8): 245–272. doi:10.1145/960118.808391.
- ^ Ross, Doug. "The first software engineering language". LCS/AI Lab Timeline. MIT Computer Science and Artificial Intelligence Laboratory. Retrieved 13 May 2010.
- ^ a b Holmevik, Jan Rune (Winter 1994). "Compiling Simula: A historical study of technological genesis" (PDF). IEEE Annals of the History of Computing. 16 (4): 25–37. doi:10.1109/85.329756. S2CID 18148999. Archived from the original (PDF) on 30 August 2017. Retrieved 3 March 2018.
- ^ Madsen, Ole Lehrman. "Kristen Nygaard". A.M. Turing Award Laureates. Retrieved 4 February 2025.
- ^ Butcher, Paul (30 June 2014). Seven Concurrency Models in Seven Weeks: When Threads Unravel. Pragmatic Bookshelf. p. 204. ISBN 978-1-68050-466-8.
- ^ Jones, Anita K.; Liskov, Barbara H. (April 1976). An Access Control Facility for Programming Languages (PDF) (Technical report). MIT. CSG Memo 137.
- ^ a b Bertrand Meyer (2009). Touch of Class: Learning to Program Well with Objects and Contracts. Springer Science & Business Media. p. 329. Bibcode:2009tclp.book.....M. ISBN 978-3-540-92144-8.
- ^ Alan C. Kay (March 1993). "The early history of Smalltalk". ACM SIGPLAN Notices. 28 (3): 69–95. doi:10.1145/155360.155364.
- ^ Borning, Alan Hamilton (1979). Thinglab: a constraint-oriented simulation laboratory (PDF) (Report). Stanford University.
- ^ Moon, David A. (June 1986). "Object-Oriented Programming with Flavors" (PDF). Conference proceedings on Object-oriented Programming Systems Languages and Applications. OOPSLA '86. pp. 1–8. doi:10.1145/28697.28698. ISBN 978-0-89791-204-4. S2CID 17150741. Retrieved 17 March 2022.
- ^ "Introducing the Smalltalk Zoo". CHM. 17 December 2020.
- ^ Bobrow, D. G.; Stefik, M. J (1982). LOOPS: data and object oriented Programming for Interlisp (PDF). European AI Conference.
- ^ Meyer 1997.
- ^ 1995 (June) Visual FoxPro 3.0, FoxPro evolves from a procedural language to an object-oriented language. Visual FoxPro 3.0 introduces a database container, seamless client/server capabilities, support for ActiveX technologies, and OLE Automation and null support. Summary of Fox releases
- ^ 1995 Reviewers Guide to Visual FoxPro 3.0: DFpug.de
- ^ Khurana, Rohit (1 November 2009). Object Oriented Programming with C++, 1E. Vikas Publishing House Pvt Limited. ISBN 978-81-259-2532-3.
- ^ Deborah J. Armstrong. The Quarks of Object-Oriented Development. A survey of nearly 40 years of computing literature identified several fundamental concepts found in the large majority of definitions of OOP, in descending order of popularity: Inheritance, Object, Class, Encapsulation, Method, Message Passing, Polymorphism, and Abstraction.
- ^ John C. Mitchell, Concepts in programming languages, Cambridge University Press, 2003, ISBN 0-521-78098-5, p.278. Lists: Dynamic dispatch, abstraction, subtype polymorphism, and inheritance.
- ^ Michael Lee Scott, Programming language pragmatics, Edition 2, Morgan Kaufmann, 2006, ISBN 0-12-633951-1, p. 470. Lists encapsulation, inheritance, and dynamic dispatch.
- ^ Pierce, Benjamin (2002). Types and Programming Languages. MIT Press. ISBN 978-0-262-16209-8., section 18.1 "What is Object-Oriented Programming?" Lists: Dynamic dispatch, encapsulation or multi-methods (multiple dispatch), subtype polymorphism, inheritance or delegation, open recursion ("this"/"self")
- ^ C. J. Date, Introduction to Database Systems, 6th-ed., Page 650
- ^ McDonough, James E. (2017). "Encapsulation". Object-Oriented Design with ABAP: A Practical Approach. Apress. doi:10.1007/978-1-4842-2838-8. ISBN 978-1-4842-2837-1 – via O'Reilly.
- ^ Bloch 2018, pp. 73–77, Chapter §4 Item15 Minimize the accessibility of classes and members.
- ^ "What is Object Oriented Programming (OOP) In Simple Words? – Software Geek Bytes". 5 January 2023. Retrieved 17 January 2023.[permanent dead link]
- ^ Cardelli, Luca; Wegner, Peter (10 December 1985). "On understanding types, data abstraction, and polymorphism". ACM Computing Surveys. 17 (4): 471–523. doi:10.1145/6041.6042. ISSN 0360-0300.
- ^ Jacobsen, Ivar; Magnus Christerson; Patrik Jonsson; Gunnar Overgaard (1992). Object Oriented Software Engineering. Addison-Wesley ACM Press. pp. 43–69. ISBN 978-0-201-54435-0.
- ^ a b c Cardelli, Luca (1996). "Bad Engineering Properties of Object-Oriented Languages". ACM Comput. Surv. 28 (4es): 150–es. doi:10.1145/242224.242415. ISSN 0360-0300. S2CID 12105785. Retrieved 21 April 2010.
- ^ a b Armstrong, Joe. Seibel, Peter (ed.). Coders at Work: Reflections on the Craft of Programming. Codersatwork.com. Archived from the original on 5 March 2010. Retrieved 13 November 2009.
- ^ a b c Raymond, Eric S. (2003). "The Art of Unix Programming: Unix and Object-Oriented Languages". Retrieved 6 August 2014.
- ^ Brodie, Leo (1984). Thinking Forth (PDF). pp. 92–93. Retrieved 4 May 2018.
- ^ Hunt, Andrew. "Don't Repeat Yourself". Category Extreme Programming. Retrieved 4 May 2018.
- ^ Bloch 2018, p. 19, Chapter §2 Item 4 Enforce noninstantiability with a private constructor.
- ^ Dony, C; Malenfant, J; Bardon, D (1999). "Classifying prototype-based programming languages" (PDF). Prototype-based programming: concepts, languages and applications. Singapore Berlin Heidelberg: Springer. ISBN 9789814021258.
- ^ "Is Go an object-oriented language?". Retrieved 13 April 2019.
Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy.
- ^ Stroustrup, Bjarne (2015). Object-Oriented Programming without Inheritance (Invited Talk). 29th European Conference on Object-Oriented Programming (ECOOP 2015). 1:34. doi:10.4230/LIPIcs.ECOOP.2015.1.
- ^ Pike, Rob (14 November 2012). "A few years ago I saw this page". Archived from the original on 14 August 2018. Retrieved 1 October 2016.
- ^ Stroustrup, Bjarne (19 February 2007). "Bjarne Stroustrup's C++ Glossary".
polymorphism – providing a single interface to entities of different types.
- ^ Martin, Robert C. "Design Principles and Design Patterns" (PDF). Archived from the original (PDF) on 6 September 2015. Retrieved 28 April 2017.
- ^ Neward, Ted (26 June 2006). "The Vietnam of Computer Science". Interoperability Happens. Archived from the original on 4 July 2006. Retrieved 2 June 2010.
- ^ C. J. Date, Hugh Darwen. Foundation for Future Database Systems: The Third Manifesto (2nd Edition)
- ^ Wirfs-Brock, Rebecca; Wilkerson, Brian (1989). "Object-Oriented Design: A Responsibility-Driven Approach". ACM SIGPLAN Notices. 24 (10): 74. doi:10.1145/74878.74885.
- ^ Karsh, Patrick (19 July 2023). "GRASP Principles: Object-Oriented Design Patterns". Medium. Retrieved 30 March 2025.
- ^ Poll, Erik. "Subtyping and Inheritance for Categorical Datatypes" (PDF). Retrieved 5 June 2011.
- ^ Abadi, Martin; Cardelli, Luca (1996). A Theory of Objects. Springer-Verlag New York, Inc. ISBN 978-0-387-94775-4. Retrieved 21 April 2010.
- ^ Tan, Tian; Li, Yue (12 July 2023). Tai-e: A Developer-Friendly Static Analysis Framework for Java by Harnessing the Good Designs of Classics. ISSTA 2023. pp. 1093–1105. doi:10.1145/3597926.3598120.
- ^ Bhutani, Vikram; Toosi, Farshad Ghassemi; Buckley, Jim (1 June 2024). "Analysing the Analysers: An Investigation of Source Code Analysis Tools". Applied Computer Systems. 29 (1): 98–111. doi:10.2478/acss-2024-0013.
- ^ a b Stepanov, Alexander (2001–2008). "STLport: An Interview with A. Stepanov". Retrieved 21 April 2010.
- ^ a b Hickey, Rich (November 2009). Are We There Yet? (keynote). JVM Languages Summit.
- ^ Pike, Rob (25 June 2012). "Less is exponentially more". Retrieved 1 October 2016.
- ^ Pike, Rob (2 March 2004). "[9fans] Re: Threads: Sewing badges of honor onto a Kernel". comp.os.plan9 (Mailing list). Retrieved 17 November 2016.
- ^ Ambler, Scott (1 January 1998). "A Realistic Look at Object-Oriented Reuse". drdobbs.com. Retrieved 5 August 2025.
- ^ Shelly, Asaf (22 August 2008). "Flaws of Object Oriented Modeling". Intel Software Network. Retrieved 4 July 2010.
- ^ James, Justin (1 October 2007). "Multithreading is a verb not a noun". techrepublic.com. Archived from the original on 10 October 2007. Retrieved 4 July 2010.
- ^ Shelly, Asaf (22 August 2008). "HOW TO: Multicore Programming (Multiprocessing) Visual C++ Class Design Guidelines, Member Functions". support.microsoft.com. Retrieved 4 July 2010.
- ^ Robert Harper (17 April 2011). "Some thoughts on teaching FP". Existential Type Blog. Retrieved 5 December 2011.
- ^ Brucker, Achim D.; Wolff, Burkhart (2008). "Extensible Universes for Object-Oriented Data Models". ECOOP 2008 – Object-Oriented Programming. Lecture Notes in Computer Science. Vol. 5142. pp. 438–462. doi:10.1007/978-3-540-70592-5_19. ISBN 978-3-540-70591-8.
object-oriented programming is a widely accepted programming paradigm
- ^ Cassel, David (21 August 2019). "Why Are So Many Developers Hating on Object-Oriented Programming?". The New Stack.
- ^ Potok, Thomas; Vouk, Mladen; Rindos, Andy (1999). "Productivity Analysis of Object-Oriented Software Developed in a Commercial Environment" (PDF). Software: Practice and Experience. 29 (10): 833–847. doi:10.1002/(SICI)1097-024X(199908)29:10<833::AID-SPE258>3.0.CO;2-P. S2CID 57865731. Retrieved 21 April 2010.
- ^ Graham, Paul. "Why ARC isn't especially Object-Oriented". PaulGraham.com. Retrieved 13 November 2009.
- ^ Feldman, Richard (30 September 2019). "Why Isn't Functional Programming the Norm?". YouTube.
- ^ Krubner, Lawrence. "Object Oriented Programming is an expensive disaster which must end". smashcompany.com. Archived from the original on 14 October 2014. Retrieved 14 October 2014.
Further reading
[edit]- Abadi, Martin; Luca Cardelli (1998). A Theory of Objects. Springer Verlag. ISBN 978-0-387-94775-4.
- Abelson, Harold; Gerald Jay Sussman (1997). Structure and Interpretation of Computer Programs. MIT Press. ISBN 978-0-262-01153-2. Archived from the original on 26 December 2017. Retrieved 22 January 2006.
- Armstrong, Deborah J. (February 2006). "The Quarks of Object-Oriented Development". Communications of the ACM. 49 (2): 123–128. doi:10.1145/1113034.1113040. ISSN 0001-0782. S2CID 11485502.
- Bloch, Joshua (2018). "Effective Java: Programming Language Guide" (third ed.). Addison-Wesley. ISBN 978-0134685991.
- Booch, Grady (1997). Object-Oriented Analysis and Design with Applications. Addison-Wesley. ISBN 978-0-8053-5340-2.
- Eeles, Peter; Oliver Sims (1998). Building Business Objects. John Wiley & Sons. ISBN 978-0-471-19176-6.
- Gamma, Erich; Richard Helm; Ralph Johnson; John Vlissides (1995). Design Patterns: Elements of Reusable Object Oriented Software. Addison-Wesley. Bibcode:1995dper.book.....G. ISBN 978-0-201-63361-0.
- Harmon, Paul; William Morrissey (1996). The Object Technology Casebook – Lessons from Award-Winning Business Applications. John Wiley & Sons. ISBN 978-0-471-14717-6.
- Jacobson, Ivar (1992). Object-Oriented Software Engineering: A Use Case-Driven Approach. Addison-Wesley. Bibcode:1992oose.book.....J. ISBN 978-0-201-54435-0.
- Kay, Alan. The Early History of Smalltalk. Archived from the original on 4 April 2005. Retrieved 18 April 2005.
- Meyer, Bertrand (1997). Object-Oriented Software Construction. Prentice Hall. ISBN 978-0-13-629155-8.
- Pecinovsky, Rudolf (2013). OOP – Learn Object Oriented Thinking & Programming. Bruckner Publishing. ISBN 978-80-904661-8-0.
- Rumbaugh, James; Blaha, Michael; Premerlani, William; Eddy, Frederick; Lorensen, William (1991). Object-Oriented Modeling and Design. Prentice Hall. ISBN 978-0-13-629841-0.
- Schach, Stephen (2006). Object-Oriented and Classical Software Engineering, Seventh Edition. McGraw-Hill. ISBN 978-0-07-319126-3.
- Schreiner, Axel-Tobias (1993). Object oriented programming with ANSI-C. Hanser. hdl:1850/8544. ISBN 978-3-446-17426-9.
- Taylor, David A. (1992). Object-Oriented Information Systems – Planning and Implementation. John Wiley & Sons. ISBN 978-0-471-54364-0.
- Weisfeld, Matt (2009). The Object-Oriented Thought Process, Third Edition. Addison-Wesley. ISBN 978-0-672-33016-2.
- West, David (2004). Object Thinking (Developer Reference). Microsoft Press. ISBN 978-0-7356-1965-4.
External links
[edit]Object-oriented programming
View on GrokipediaOverview
Definition and Goals
Object-oriented programming (OOP) is a programming paradigm that organizes software design around data, or objects, rather than functions and logic, where objects encapsulate both data attributes and the methods that operate on that data to represent real-world or abstract entities.[5] This approach models entities as self-contained units that interact through well-defined interfaces, enabling developers to simulate complex behaviors and relationships in code.[6] The historical goals of OOP emerged from the need to simulate large numbers of interacting entities in discrete event simulations, promoting code reuse by allowing similar objects to share behaviors and structures, while enhancing maintainability through modular organization that simplifies updates in evolving systems.[7] By focusing on hierarchies and interactions among objects, OOP facilitates the modeling of complex systems, such as those in scientific simulations or large-scale applications, where traditional procedural methods relying on sequential functions struggle with scalability.[8] In OOP terminology, a class serves as a blueprint or template defining the structure and behavior for objects, while an object is an instance of a class, representing a specific entity with its own state.[9] For example, consider a simple class for a "Car" in pseudocode:class Car {
attribute speed: integer = 0
attribute color: string
method accelerate(amount: integer) {
speed = speed + amount
}
method getSpeed(): integer {
return speed
}
}
class Car {
attribute speed: integer = 0
attribute color: string
method accelerate(amount: integer) {
speed = speed + amount
}
method getSpeed(): integer {
return speed
}
}
speed and color, and methods such as accelerate to modify the object's state, illustrating how data and operations are bundled together.[5]
The primary objectives of OOP include achieving modularity by bundling related data and operations within objects, which reduces complexity and supports code reusability across projects.[10] This modularity also facilitates scalability in large software systems, as changes to one object's implementation do not necessarily affect others, promoting easier maintenance and extension over time.[6]
Comparison to Other Paradigms
Object-oriented programming (OOP) contrasts with procedural programming by organizing code around objects that encapsulate data and behavior, rather than relying on sequential procedures and functions that operate on global data structures. This object-centric approach introduces modularity through classes and instances, which helps mitigate issues like unintended modifications to shared global state that are common in procedural code, where functions often access and alter data in a linear fashion.[11] In comparison to functional programming, OOP prioritizes mutable state encapsulated within objects, allowing for dynamic changes to data through methods, whereas functional programming emphasizes immutable data structures and pure functions that avoid side effects to ensure predictability and composability. This difference makes OOP suitable for modeling complex, stateful systems, but functional approaches excel in scenarios requiring mathematical rigor or parallelism due to their stateless nature. Hybrid languages like Scala blend these paradigms, supporting both object-oriented features and functional constructs such as higher-order functions.[12] OOP is fundamentally imperative and object-focused, specifying how computations occur through sequences of operations on objects, in contrast to declarative paradigms like logic programming, which define what the desired outcome is via rules and facts, leaving the execution details to the underlying inference engine.[13] OOP demonstrates particular strengths in domains such as graphical user interface (GUI) development, where its encapsulation and inheritance enable reusable components for handling events and layouts, improving maintainability and extensibility. Similarly, in simulations, OOP facilitates modular hierarchies of entities (e.g., vehicles and their subsystems), supporting concurrent execution and easy customization through polymorphism, which enhances scalability in modeling real-world dynamics.[14][15] However, OOP's reliance on mutable state can complicate concurrent programming, increasing risks of race conditions and deadlocks in multi-threaded environments, where functional programming's immutability provides safer parallelism. In mathematical computing, OOP's state-oriented design hinders formal verification and deterministic reasoning, making functional paradigms preferable for pure, composable computations.[16] The rise of multi-paradigm languages like Python and Java has enabled blending OOP with procedural and functional elements, allowing developers to select paradigms based on context—such as using OOP for structured modeling alongside functional utilities for data processing—thus combining reusability with flexibility.[17]History
Early Concepts and Influences
The roots of object-oriented programming (OOP) trace back to mid-20th-century efforts in mathematics and computer science aimed at managing program complexity through modular and abstract structures. In the 1960s, concepts of data abstraction emerged from algebraic approaches to programming, emphasizing the separation of data representation from operations, as seen in early work on record handling that influenced later type systems.[18] Concurrently, the structured program theorem by Corrado Böhm and Giuseppe Jacopini demonstrated that any computable function could be expressed using sequence, selection, and iteration, providing a foundational framework for disciplined, modular code organization that addressed the limitations of unstructured programming and laid groundwork for encapsulating behaviors in larger systems.[19] Early simulation languages further shaped OOP by prioritizing entity-based modeling of dynamic systems. The JOSS (Johnniac Open Shop System) language, developed in the early 1960s at RAND Corporation, introduced interactive, time-sharing capabilities for problem-solving and basic simulations, encouraging a focus on user-defined entities and procedures that prefigured object-like modularity in handling real-world processes.[20] Similarly, pioneering graphics systems in the 1960s emphasized modeling entities with inherent properties and interactions; Ivan Sutherland's Sketchpad (1963), created at MIT, allowed users to define graphical "objects" such as lines and circles with attributes like position and constraints, supporting instantiation from master definitions—a direct precursor to classes and instances in OOP.[21] A pivotal precursor was Simula, developed by Ole-Johan Dahl and Kristen Nygaard at the Norwegian Computing Center starting in 1962 and culminating in Simula 67. Designed specifically for discrete event simulation—modeling systems like traffic flows or production lines through sequential events—Simula introduced the class construct to represent simulatable entities (processes) with encapsulated state and behavior, enabling hierarchical extensions and dynamic activation, which formalized the idea of reusable, self-contained modules for complex simulations.[22] Alan Kay, while at the University of Utah in the late 1960s, envisioned OOP as a paradigm for simulating biological and social systems, drawing inspiration from Simula's classes and Sketchpad's interactive entities. He conceptualized objects as autonomous agents communicating via messages, akin to cells in a biological organism exchanging signals without direct internal access, an idea that crystallized in his work on Smalltalk and coined the term "object-oriented programming" around 1967 to describe this messaging-oriented approach to building scalable, adaptive software.[23] Philosophically, OOP's emphasis on modeling the world as networks of interacting, autonomous entities echoes cybernetics and systems theory from the 1940s onward. Cybernetics, as articulated by Norbert Wiener, explored feedback and control in self-regulating systems, while general systems theory, advanced by Ludwig von Bertalanffy, promoted holistic views of interconnected components over reductionist analysis; these ideas influenced OOP's focus on emergent behavior from object interactions in simulating real-world complexity.[24]Key Developments and Languages
The development of object-oriented programming (OOP) began in the late 1960s with Simula, widely recognized as the first OOP language, created by Ole-Johan Dahl and Kristen Nygaard at the Norwegian Computing Center to support simulation modeling. Simula introduced classes, objects, and inheritance, enabling the modeling of real-world entities in a structured way, which laid the foundational syntax and semantics for subsequent OOP systems. In the 1970s, Smalltalk emerged at Xerox PARC under Alan Kay and his team, pioneering a pure OOP approach where everything—from primitives to control structures—is treated as an object, emphasizing message passing and dynamic behavior. Smalltalk's influence extended beyond language design, inspiring graphical user interfaces and windowing systems that popularized OOP concepts in interactive computing environments. The 1980s marked a boom in OOP languages that integrated with existing procedural paradigms. C++, developed by Bjarne Stroustrup starting in 1979 at Bell Labs, extended the C language with classes, inheritance, and polymorphism while maintaining backward compatibility, facilitating OOP adoption in systems programming and performance-critical applications. Objective-C, introduced in 1984 by Brad Cox and Tom Love, added Smalltalk-like messaging to C, gaining traction in enterprise software for its dynamic runtime features. Eiffel, released in 1986 by Bertrand Meyer at Interactive Software Systems, emphasized software engineering principles through "design by contract," incorporating preconditions, postconditions, and invariants to enhance reliability. The 1990s saw widespread industry adoption, driven by languages prioritizing portability and ease of use. Java, unveiled by Sun Microsystems in 1995 under James Gosling, achieved platform independence via the Java Virtual Machine and bytecode, making OOP accessible for web and enterprise development. Python, created by Guido van Rossum in the late 1980s and first released publicly in 1991, offered intuitive OOP syntax with classes and multiple inheritance, promoting accessibility for scripting and rapid prototyping. A key milestone was Java's integration with web technologies, where applets in the late 1990s enabled dynamic, cross-platform content in browsers, accelerating OOP's mainstream acceptance in client-server architectures. By the 2000s and into 2025, OOP evolved through hybrid languages blending it with functional and systems paradigms, without introducing major new pure OOP languages. Scala, launched in 2004 by Martin Odersky at EPFL, incorporated traits—reusable units of behavior akin to mixins—for flexible composition, running on the JVM to leverage Java's ecosystem. Rust, developed by Graydon Hoare and stabilized in 2015 by Mozilla, uses traits for polymorphism and ownership, combining OOP elements with memory safety for systems programming. Integrations in ecosystems like Microsoft's .NET framework (since 2002) and the JVM have sustained OOP's relevance, supporting multi-language environments for enterprise-scale applications up to contemporary cloud and distributed systems.Core Features
Objects, Classes, and Instances
In object-oriented programming, a class serves as a blueprint or template that defines the structure and behavior for a category of objects, specifying attributes (data fields) and methods (procedures or functions that operate on those attributes).[25] This concept originated in Simula 67, where a class declaration outlines the local data and actions for objects, enabling modular simulation of real-world entities.[25] An object, in turn, is a runtime instance of a class, embodying a concrete realization of that template with its own allocated memory for data and the ability to execute defined methods.[3] In Smalltalk, for example, objects are atomic entities created from class templates, emphasizing encapsulation of state and behavior as foundational to the paradigm.[23] Objects are created through a process known as instantiation, typically initiated by invoking a special method called a constructor that initializes the object's state upon allocation.[26] In many languages, thenew operator allocates memory for the object and automatically calls the constructor to set initial attribute values.[26] For instance, consider a pseudocode example of a Person class:
class Person {
attribute name: String
constructor(name: String) {
this.name = name
}
method greet() {
return "Hello, my name is " + this.name
}
}
person1 = new Person("Alice")
person1.greet() // Outputs: "Hello, my name is Alice"
class Person {
attribute name: String
constructor(name: String) {
this.name = name
}
method greet() {
return "Hello, my name is " + this.name
}
}
person1 = new Person("Alice")
person1.greet() // Outputs: "Hello, my name is Alice"
name attribute and greet method, while instantiation produces a usable object.[26] Constructors ensure objects start in a valid state, often with parameters to customize initialization.[27]
Each object possesses a unique identity, which distinguishes it from others even if their states are identical, typically represented by a memory address or reference.[28] The state of an object refers to the current values of its attributes, which are generally mutable, allowing changes through method calls that reflect evolving conditions.[22] Within a class, instance members (attributes and methods tied to specific objects) maintain per-object state, whereas static members belong to the class itself and are shared across all instances, independent of any particular object.[29] This distinction supports efficient resource use, as static members avoid redundant storage.[30]
A single class can yield multiple instances, each sharing the class's definition but maintaining independent states to model distinct entities without interference.[31] For example, two Person objects from the same class can have different name values, enabling scalable representation of collections like user accounts.[31] In languages like Java, memory management for these objects relies on automatic garbage collection, which identifies and reclaims memory from unreachable instances (those without active references), preventing leaks and simplifying development.[32] This mechanism ensures that the runtime environment handles deallocation, allowing programmers to focus on object creation and interaction.[32]
Encapsulation and Information Hiding
Encapsulation in object-oriented programming refers to the bundling of data attributes and the methods that operate on them within a single unit, known as an object or class.[33] This approach organizes related elements together, promoting modularity by treating the object as a self-contained entity.[34] By grouping data and procedures, encapsulation reduces system complexity, as changes to an object's internal workings can be localized without affecting external code.[34] It also facilitates easier debugging, since errors are confined to specific objects rather than scattered across the program.[34] Information hiding complements encapsulation by restricting direct access to an object's internal details, thereby protecting its integrity and simplifying interactions.[35] This principle, introduced as a modularization criterion, emphasizes concealing implementation specifics that are likely to change, allowing users to interact only with a stable interface.[35] In practice, languages implement information hiding through access modifiers such as public, private, and protected, which control visibility of class members.[14] Public members are accessible from anywhere, private ones only within the class, and protected ones within the class and its subclasses.[14] Private fields, for instance, are typically accessed indirectly via public methods like getters and setters, enforcing controlled interactions.[14] A core principle of this approach is the separation of interface from implementation, where the external view of an object remains unchanged even if its internals evolve.[34] This supports maintainability by minimizing dependencies on hidden details. Additionally, the YAGNI ("You Ain't Gonna Need It") guideline advises against exposing more functionality than necessary, reducing complexity and potential misuse.[36] Consider a BankAccount class as an illustrative example: the balance attribute is declared private to hide it from direct manipulation, while public methods like deposit() and withdraw() provide controlled access, ensuring valid transactions such as preventing negative balances.[14] This encapsulation safeguards sensitive data and maintains the object's consistency.Abstraction
In object-oriented programming (OOP), abstraction refers to the process of identifying the essential characteristics of an entity while disregarding extraneous details, thereby simplifying complex systems into manageable models. This principle enables developers to represent real-world objects or concepts through software entities that capture only the relevant behaviors and attributes, fostering a higher-level view of the program's structure. Abstraction operates at multiple levels: data abstraction, achieved via classes that encapsulate state and operations, and process abstraction, realized through methods that define actions without exposing underlying implementation intricacies.[9][37] Key mechanisms for implementing abstraction in OOP include abstract classes and interfaces. An abstract class provides a partial blueprint for derived classes, including some concrete methods alongside unimplemented abstract methods that must be overridden by subclasses; it cannot be instantiated directly, serving as a foundation for related classes. Interfaces, in contrast, define pure contracts consisting solely of method signatures without any implementation, enforcing a standard set of behaviors that implementing classes must fulfill. For instance, consider aShape interface declaring a draw() method; classes like Circle and Rectangle then provide concrete implementations, allowing polymorphic usage of shapes without concern for specific drawing algorithms.[38][39]
The benefits of abstraction in OOP are significant for software design and maintenance. By exposing only necessary interfaces, it promotes loose coupling between components, reducing dependencies and enabling modules to interact through well-defined boundaries rather than internal specifics. This facilitates easier code evolution, as changes to an object's internal algorithms or data representations can occur without impacting dependent code, enhancing reusability and scalability in large systems. Abstraction also plays a crucial role in hiding implementation details, complementing encapsulation to protect object internals from external interference.[40][41]
In comparison to procedural programming, where abstraction primarily relies on functions and modules for isolating procedures, OOP integrates abstraction directly with objects, providing contextual relevance by bundling data and behavior. This object-centric approach allows for more intuitive modeling of domain-specific problems, as abstractions are tied to entities that mirror real-world interactions rather than standalone routines.[42][43]
Composition
In object-oriented programming, composition refers to the mechanism of building complex objects by aggregating simpler objects as parts, establishing a "has-a" relationship rather than the "is-a" relationship of inheritance. For instance, a Car class might compose an Engine object and multiple Wheel objects, where the Car is responsible for coordinating these components without implying that a Car is a type of Engine. This approach promotes modular design by allowing objects to be constructed from independent, reusable parts that can be swapped or modified without altering the containing class's structure.[44] Composition manifests in two primary forms: aggregation and composition proper, both subtypes of association in UML. Aggregation represents a weaker "has-a" relationship where the part objects can exist independently and may be shared among multiple wholes, such as a University aggregating Student objects that could belong to other institutions. In contrast, composition denotes a stronger ownership where the lifecycle of the part is tightly coupled to the whole; if the containing object is destroyed, its composed parts are also destroyed, as in a House composing Room objects that cease to exist meaningfully without the house. UML notation distinguishes these with an open diamond at the whole end for aggregation and a filled black diamond for composition, emphasizing the degree of dependency.[45] The benefits of composition include enhanced flexibility in system design, as components can be assembled dynamically at runtime, and improved testability by isolating units for independent verification. This aligns with the principle of favoring composition over inheritance to avoid rigid hierarchies that can lead to brittle code, enabling better reuse through delegation to composed objects rather than extending base classes. For example, a House class might compose multiple Room instances, each of which in turn composes Furniture objects, allowing the house's structure to evolve without subclassing for every variation. In implementation, composition is typically achieved by declaring fields or attributes in a class that hold references to instances of other classes, with constructors or setters initializing these references. Managing object lifecycles requires careful attention, such as ensuring composed parts are properly disposed of when the whole is deallocated to prevent memory leaks, often handled via destructors in languages like C++ or garbage collection in Java. This reference-based assembly reinforces the encapsulation of object structure, where internal components are accessed through the containing object's interface.[46]Inheritance
Inheritance in object-oriented programming is a mechanism that enables a new class, referred to as a subclass or derived class, to acquire state and behavior from an existing class, known as a superclass or base class, thereby establishing an "is-a" relationship between them.[47] This allows the subclass to reuse the superclass's attributes and methods while potentially adding or overriding them to provide specialized functionality.[47] Single inheritance, in which a subclass derives from exactly one superclass, forms a tree-like hierarchy that simplifies understanding and implementation by avoiding issues such as name collisions and repeated inheritance. Multiple inheritance, by contrast, permits a subclass to derive from more than one superclass, forming a directed acyclic graph (DAG) that enhances modularity and reusability—such as combining independent protocols or separating interfaces from implementations—but introduces complexities like the diamond problem. The diamond problem arises when a subclass inherits a common ancestor through multiple paths, resulting in ambiguities over which version of inherited members to use, as seen in scenarios where two intermediate classes both extend the same base class. In class-based languages like Java and C++, inheritance relies on explicit class declarations and hierarchies, where a subclass uses keywords such asextends in Java to declare its superclass and inherit its structure.[47] Prototype-based languages, such as JavaScript, achieve inheritance through delegation via a prototype chain, where an object's properties are resolved by traversing links to prototype objects rather than static classes.[48]
Behavioral subtyping ensures the integrity of inheritance hierarchies by requiring that subtypes adhere to the expected behavior of their supertypes. The Liskov Substitution Principle (LSP), articulated by Barbara Liskov and Jeannette Wing, posits that a subtype must be substitutable for its supertype in any context without altering the program's correctness or observable behavior.[49] This principle emphasizes that any property provable about supertype objects must hold for subtype objects, guiding the design of substitutable types through specifications of preconditions, postconditions, and invariants.[49] To mitigate inheritance-related risks without full class hierarchies, languages provide alternatives like interfaces, which enable multiple "inheritance" of contracts (method signatures) without inheriting implementation details.[50]
Inheritance promotes code reuse and specialization, allowing developers to build upon existing classes to create more specific ones, thereby reducing redundancy and improving maintainability in hierarchical designs.[47] However, it fosters tight coupling, where changes to a superclass can unexpectedly break subclasses, and deep hierarchies exacerbate fragility and maintenance challenges.[51] For instance, overriding a superclass method may invert the intended "is-a" relationship if it alters expected behavior, leading to subtle bugs.[51]
A representative example is an Animal superclass with a general eat() method for consuming food:
public class Animal {
public void eat() {
System.out.println("This animal eats food.");
}
}
public class Animal {
public void eat() {
System.out.println("This animal eats food.");
}
}
Dog subclass extends Animal to inherit eat() while adding a bark() method:
public class Dog extends Animal {
public void bark() {
System.out.println("The dog barks.");
}
}
public class Dog extends Animal {
public void bark() {
System.out.println("The dog barks.");
}
}
Dog reuses the eating behavior and specializes it with unique actions.[47] While inheritance excels in modeling "is-a" relationships, composition serves as an alternative for "has-a" relationships, enabling flexible aggregation of behaviors without the coupling of hierarchies.[51]
Polymorphism
Polymorphism in object-oriented programming (OOP) refers to the ability of objects belonging to different classes to be treated uniformly through a common interface, allowing the same operation to produce different behaviors depending on the object's type.[14] This concept enables code to work with objects of various types without needing to know their exact class, promoting flexibility and reuse.[15] In OOP, polymorphism is primarily realized through three types: ad-hoc, parametric, and subtype, with a particular emphasis on runtime polymorphism achieved via virtual methods.[52] Ad-hoc polymorphism, also known as overloading, allows functions or methods with the same name to operate on different types by providing type-specific implementations, resolved at compile time.[53] Parametric polymorphism, often implemented through generics, enables writing code that works uniformly across multiple types without specifying them in advance, also typically resolved at compile time.[54] Subtype polymorphism, central to OOP hierarchies, permits objects of a derived class to be substituted for those of a base class, with method calls invoking the overridden implementation in the derived class at runtime via virtual methods.[55] This runtime form, often called dynamic or inclusion polymorphism, leverages inheritance to allow seamless substitution while ensuring behavioral correctness.[55] The mechanism of polymorphism involves objects from different classes responding to the same method call, or "message," in a context-specific manner. For instance, consider a class hierarchy where a base classShape defines a virtual method draw(), which is overridden in subclasses like Circle and Rectangle. A loop iterating over a collection of Shape objects can invoke draw() uniformly, with each object executing its own implementation—Circle rendering a circle and Rectangle a rectangle—without the caller distinguishing between them.[56] This uniform treatment relies on the runtime system selecting the appropriate method based on the actual object type, enabling polymorphic behavior.
Polymorphism enhances code flexibility by allowing extensions or modifications without altering existing client code, adhering to principles like the open-closed principle.[15] It supports extensibility in frameworks, such as plugin systems, where new components implement a common interface and integrate seamlessly without recompiling the core system.[15] Overall, it fosters maintainable, scalable software by reducing coupling and promoting abstraction over concrete types.[57]
However, polymorphism introduces limitations, particularly in performance-critical applications, due to the overhead of runtime method resolution and virtual function calls, which can increase execution time compared to static dispatch. This dynamic nature may also complicate debugging and optimization in low-level or real-time systems.[57]
Dynamic Dispatch
Dynamic dispatch is a runtime mechanism in object-oriented programming that selects the appropriate method implementation based on the actual type of the object invoking the method, rather than its declared static type. This process enables polymorphic behavior by resolving method calls at invocation time, contrasting with static binding, which determines the method at compile time based on the reference type. In class-based languages like C++, dynamic dispatch is typically implemented using virtual method tables (vtables), where each class maintains a table of pointers to its virtual functions, and every object contains a hidden pointer (vptr) to its class's vtable. Upon a virtual method call, the runtime uses the object's vptr to access the vtable and invoke the corresponding function pointer, incurring an indirection cost that involves memory access and potential cache misses.[58] In C++, virtual functions exemplify dynamic dispatch, where declaring a method as virtual in a base class allows subclasses to override it, and calls through base pointers or references resolve to the overridden implementation at runtime. For instance, consider a base classShape with a virtual method draw(), overridden in derived classes like Circle and Square; a pointer of type Shape* pointing to a Circle object will invoke Circle::draw() via the vtable lookup. This indirection adds overhead—typically 5-10 CPU cycles per call due to pointer dereferencing and branch prediction—but is mitigated in modern compilers through optimizations like devirtualization when the type is known. The performance implication arises from the extra memory load and prevention of certain inlining, making dynamic dispatch suitable for interfaces with high polymorphism but less ideal for hot paths with predictable types.[58][59]
Most object-oriented languages employ single dispatch, where the method selection depends solely on the runtime type of the receiver (the object on which the method is called), simplifying implementation via per-class vtables. In contrast, multiple dispatch extends this to consider the types of multiple arguments, as in the Common Lisp Object System (CLOS), where generic functions select methods based on all required arguments' types, enabling more expressive polymorphism for operations like arithmetic on mixed numeric types. CLOS achieves this through a method combination protocol that discriminates on argument types at runtime, though it increases dispatch complexity compared to single dispatch.[60][61]
Dynamic dispatch facilitates late binding, where method bindings are deferred until runtime, directly supporting runtime polymorphism by allowing the same interface to invoke different behaviors based on the object's dynamic type without compile-time knowledge of the exact class. This contrasts with early binding in non-virtual calls and is essential for extensible hierarchies, though it requires careful design to balance flexibility and performance.[58][62]
Advanced Mechanisms
Message Passing
In object-oriented programming, message passing serves as the fundamental mechanism for inter-object communication, treating objects as autonomous, active entities that exchange messages to request actions or share information. This paradigm conceptualizes objects as encapsulating both state and behavior, interacting solely through messages rather than direct access to internal details, thereby promoting modularity and abstraction. The concept draws inspiration from biological systems, where cells function as self-contained units communicating via signals across protective membranes to coordinate activities without exposing their internal processes.[63] In pure implementations, such as Smalltalk, message passing is the core model: every operation, including arithmetic and control flow, is expressed as a message sent from one object to another, with the receiver dynamically interpreting the message to invoke an appropriate response. This uniform approach views computation as a network of collaborating objects, where messages include a selector (indicating the desired action) and arguments, enabling flexible and extensible interactions. In contrast, hybrid languages like Java frame object interactions primarily as method invocations on references, which implement message passing in a more structured, synchronous manner but retain elements of dynamism through virtual method resolution.[63] Message passing can be synchronous or asynchronous, depending on the execution model. Synchronous passing, common in direct method calls, blocks the sender until the receiver processes the message and returns a result, ensuring immediate feedback as seen in standard OOP interactions. Asynchronous variants, prevalent in event-driven systems like graphical user interfaces, allow the sender to continue without waiting, with responses handled via callbacks or queues, enhancing responsiveness in concurrent environments. Extensions like the actor model further this through asynchronous message passing in distributed settings; for instance, Akka in Scala implements actors as lightweight objects that process immutable messages in isolated mailboxes, isolating failures and scaling across nodes.[64] A key benefit of message passing is the decoupling of sender from receiver, as the sender specifies only the intent via the message without knowledge of the receiver's implementation, fostering loose coupling, reusability, and maintainability. For example, a client object might send a "print" message to a printer object, passing document data as arguments; the printer handles the request internally, potentially delegating to hardware or other objects, while the client remains agnostic to these details. This separation aligns with dynamic method selection, where the receiver determines the exact behavior at runtime based on its type.[63] The evolution of message passing traces from Alan Kay's biologically inspired vision of collaborative, protective entities in Smalltalk to contemporary patterns like publish-subscribe (pub-sub) systems, which extend asynchronous messaging for scalable, one-to-many distribution in distributed OOP applications. In pub-sub models, publishers broadcast messages to topics without addressing specific subscribers, who register interest via filters, enabling decoupled event propagation in frameworks handling real-time data streams or microservices.Open Recursion
Open recursion is a mechanism in object-oriented programming that enables a method in a class to invoke another method on the same object using a late-bound reference, such asself or this, allowing subclasses to override and extend the invoked method dynamically at runtime.[65] This contrasts with closed recursion, where method calls are resolved statically at compile time based on lexical scoping, tying the recursive behavior directly to the defining context rather than the runtime object.[65] In open recursion, the call site remains "open" to interception by subclasses, facilitating behavioral augmentation without requiring the base class to be rewritten.[65]
A practical example appears in Java, where a subclass overrides a superclass method and uses the super keyword to invoke the parent's implementation while adding its own logic, leveraging open recursion for extension.[66] For instance, consider a base class Shape with a draw() method that calls a validate() method on this; a subclass Circle can override validate() to include shape-specific checks, and the draw() call to validate() will dynamically resolve to the subclass version.[66] However, this flexibility introduces risks, such as the fragile base class problem, where changes to the superclass's internal method calls can unexpectedly alter subclass behavior due to the dynamic dispatch on this.[66]
Open recursion plays a crucial role in frameworks by enabling aspect-oriented extensions, such as the template method pattern, where a base framework method invokes hook methods that subclasses can override to inject custom behavior without modifying the core logic.[66] This supports modular extensions in large systems, allowing developers to adapt framework functionality through inheritance while preserving the base implementation's integrity.[66]
Most class-based object-oriented languages, including Java, C++, and Smalltalk, natively support open recursion through dynamic dispatch on self or this.[65] In prototype-based systems, the mechanism is nuanced, often relying on delegation chains that approximate late binding but may not enforce the same class hierarchy semantics.[65]
Prototype-Based Approaches
Prototype-based approaches to object-oriented programming eschew classes in favor of direct inheritance from other objects, known as prototypes, enabling a more fluid model of object creation and behavior sharing. In this paradigm, every object serves as a potential prototype, and new objects are typically created by cloning an existing one, which establishes a delegation link to the original for unresolved property or method lookups. This delegation forms a chain where, if a requested property is absent in the current object, the search proceeds to its prototype, and so on, until resolved or reaching the end of the chain.[67][48] The core mechanism relies on this prototype chain for delegation, allowing objects to inherit behavior dynamically without predefined hierarchies. For instance, cloning a "vehicle" prototype to create a "car" object involves copying the base properties and linking the new object to the prototype; any missing methods, such as "drive," would delegate to the vehicle's implementation. Languages like Self implement this through parent slots that point to prototypes, unifying state and behavior access in a single structure. Similarly, Io uses a list of prototypes (protos) for delegation, with message lookup traversing them depth-first, and employs a clone method that performs create-on-write to initialize new objects efficiently. In JavaScript, the [[Prototype]] internal slot establishes the chain, and Object.create() facilitates cloning by setting a specified prototype, enabling lightweight extension without constructors.[67][68][69] This approach offers advantages in dynamism and simplicity, avoiding the rigidity of class definitions by treating all objects uniformly, which supports the creation of one-of-a-kind instances with minimal overhead. It eliminates distinctions between classes and instances, fostering exploratory programming where objects can be extended or modified at runtime more intuitively than in class-based systems. For example, in Self, the absence of classes reduces conceptual complexity to a single "inherits from" relation, making it easier to build unique objects without subclass proliferation. However, compared to class-based systems, prototype-based methods provide greater flexibility for runtime changes but can complicate reasoning about code due to mutable prototypes and potential for unintended delegations, leading to less predictable inheritance paths.[67][68] In modern usage, particularly web development, JavaScript's prototype-based foundation remains central despite ES6 classes serving as syntactic sugar over it, allowing efficient sharing of methods across instances to conserve memory in browser environments. This model excels in scenarios requiring ad-hoc object extension, such as dynamic UI components, where cloning and delegating from base prototypes enables rapid prototyping without rigid blueprints. Languages like Self and Io, while influential, see primary application in research and niche scripting, underscoring the paradigm's role in emphasizing concrete, evolvable objects over abstract templates.[48][70]Design Principles and Patterns
Common Design Patterns
Design patterns in object-oriented programming provide templated, reusable solutions to commonly recurring problems in software design, promoting flexibility, maintainability, and code reuse by encapsulating best practices for object creation, structure, and interaction. These patterns are categorized into creational, structural, and behavioral types, though the focus here is on creational and structural patterns that leverage OOP principles like encapsulation and composition to build modular systems. Originating from the seminal work by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, these patterns abstract complex interactions into named, proven strategies that developers can apply without starting from scratch. Creational patterns address object instantiation, decoupling the creation process from the specific classes involved to enhance flexibility. The Singleton pattern ensures that a class has only one instance and provides a global point of access to it, useful for managing shared resources like configuration managers or loggers in applications where multiple instances would lead to inconsistencies. For example, in a database connection scenario, the Singleton might be implemented with a private constructor and a static method returning the single instance, preventing redundant connections. The Factory Method pattern defines an interface for creating an object but allows subclasses to decide which class to instantiate, thereby deferring instantiation to subclasses and supporting the open-closed principle in OOP. A common example is a document application where a factory method creates different types of documents (e.g., text or chart) based on user input, represented in UML as an abstract creator class with a factory method overridden in concrete subclasses, linked to product interfaces. Structural patterns focus on composing classes and objects into larger structures while keeping them flexible and efficient, often emphasizing composition over inheritance to achieve modularity. The Adapter pattern converts the interface of a class into another interface that clients expect, allowing incompatible classes to work together seamlessly, such as adapting a legacy XML parser to a modern JSON-based API. In UML terms, this involves a target interface, an adaptee with its own interface, and an adapter class that implements the target and delegates to the adaptee, forming a composition relationship. The Decorator pattern enables dynamic attachment of new behaviors to objects by wrapping them in decorator classes that implement the same interface, providing a flexible alternative to subclassing for extending functionality. For instance, in a graphics editor, a basic shape like a circle can be decorated with borders or fills via chained decorator objects, depicted in UML as a component interface with concrete components and decorators that hold references to components and add behaviors before or after delegation.[71] Among object-focused structural patterns, the Flyweight pattern minimizes memory usage by sharing as much state as possible among similar objects, distinguishing between intrinsic (shared, immutable) state stored in flyweight objects and extrinsic (context-dependent) state passed at runtime. This is particularly valuable in scenarios with large numbers of fine-grained objects, such as rendering thousands of tree instances in a forest simulation, where shared flyweight objects hold common attributes like texture while unique positions are extrinsic. UML representation typically shows a flyweight factory managing a pool of flyweight instances, with client objects composing flyweights and providing extrinsic state during operations.[72] By emphasizing composition, the Flyweight pattern aligns with OOP's core feature of building complex objects from simpler, reusable parts.[72] The adoption of these common design patterns yields significant benefits in object-oriented programming, including reduced complexity through standardized solutions that avoid reinventing the wheel, improved system modularity by promoting loose coupling and encapsulation, and enhanced scalability as patterns facilitate easier extension and maintenance of codebases. These advantages stem from their role in leveraging OOP's inherent modularity, allowing developers to focus on domain-specific logic rather than low-level structural concerns.Gang of Four Patterns
The Gang of Four (GoF) patterns refer to the 23 reusable solutions to common problems in object-oriented software design, as cataloged in the seminal 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.[73] This work, often abbreviated as the GoF book, emerged from the authors' experiences in developing object-oriented frameworks and tools, providing a shared vocabulary for communicating design intentions among developers.[73] The patterns are organized into three main categories—creational, structural, and behavioral—each addressing distinct aspects of object creation, composition, and interaction to promote flexibility, maintainability, and reusability in OOP systems.[73] Creational patterns focus on object instantiation mechanisms, decoupling the creation process from the specific classes involved. Key examples include Abstract Factory, which provides an interface for creating families of related objects without specifying their concrete classes; Builder, which separates the construction of complex objects from their representation to allow the same process to create different representations; Factory Method, which defines an interface for creating objects but lets subclasses decide which class to instantiate; Prototype, which creates new objects by cloning existing instances to avoid subclass proliferation; and Singleton, which ensures a class has only one instance and provides global access to it.[73] Structural patterns deal with class and object composition to form larger structures while keeping them flexible. Notable ones are Adapter, which allows incompatible interfaces to work together by wrapping one in a compatible interface; Bridge, which decouples an abstraction from its implementation so both can vary independently; Composite, which composes objects into tree structures to represent part-whole hierarchies uniformly; Decorator, which attaches additional responsibilities to objects dynamically; Facade, which provides a unified interface to a set of interfaces in a subsystem; Flyweight, which shares fine-grained objects to reduce memory usage; and Proxy, which controls access to an object by acting as a surrogate.[73] Behavioral patterns, which emphasize algorithms and object responsibilities, are particularly influential for advanced OOP uses, such as managing interactions and state changes. Observer defines a one-to-many dependency between objects, ensuring that when one object changes state, all dependents are notified and updated automatically, commonly used in event-handling systems.[73] Strategy enables families of algorithms to be defined, encapsulated, and made interchangeable, allowing the algorithm to vary independently from clients that use it, such as sorting routines.[73] Command encapsulates a request as an object, thereby parameterizing clients with queues, requests, and operations, supporting undoable actions in user interfaces.[73] Other behavioral patterns include Chain of Responsibility, which passes requests along a chain of handlers; Interpreter, for defining a grammar for simple languages; Iterator, which provides a way to access elements sequentially without exposing underlying representation; Mediator, which defines how objects interact via a mediator object to reduce coupling; Memento, which captures and externalizes an object's internal state without violating encapsulation; State, which allows an object to alter its behavior when its internal state changes; Template Method, which defines the skeleton of an algorithm in a method, deferring some steps to subclasses; and Visitor, which represents an operation to be performed on elements of an object structure, allowing new operations without changing the classes, ideal for operations on complex hierarchies like abstract syntax trees.[73] In modern programming languages, features like lambda expressions and higher-order functions have simplified implementations of several GoF patterns, particularly behavioral ones, by reducing boilerplate code and enabling more concise expressions of functionality. For instance, in Java 8 and later, lambdas can replace explicit class implementations for Strategy and Command, making code more succinct without altering the underlying design intent. Despite their benefits, the GoF patterns have faced criticisms for potential overuse, which can introduce unnecessary complexity and increase maintenance costs in software systems. A systematic mapping study of over a decade of research identified "bad smells" in pattern implementations, such as excessive abstraction layers, that counteract the intended reusability and lead to higher refactoring efforts.[74] This overuse often stems from applying patterns prematurely or inappropriately, resulting in over-engineered solutions that obscure simple logic.[74]SOLID and GRASP Principles
The SOLID principles represent a foundational set of five design guidelines for object-oriented programming, aimed at making software designs more understandable, flexible, and maintainable. The principles were introduced by Robert C. Martin in his 2000 paper, with the acronym SOLID coined by Michael Feathers around 2004.[75][76] These principles emphasize managing dependencies and promoting modularity to reduce complexity in evolving systems. The Single Responsibility Principle (SRP) states that a class should have only one reason to change, meaning it should encapsulate a single, well-defined responsibility to avoid coupling multiple concerns that could lead to unintended side effects during modifications.[75] This principle supports high cohesion by ensuring classes remain focused, facilitating easier testing and maintenance.[75] The Open-Closed Principle (OCP) posits that software entities should be open for extension but closed for modification, achieved through abstraction and polymorphism to allow new behavior without altering existing code.[75] It promotes reusability by isolating extensions to derived classes or interfaces, minimizing regression risks in large codebases.[75] The Liskov Substitution Principle (LSP) requires that objects of a superclass should be replaceable with objects of a subclass without altering the correctness of the program, enforcing behavioral compatibility in inheritance hierarchies.[75] Violations, such as weakening preconditions or strengthening postconditions in subtypes, can introduce subtle bugs, making this principle essential for reliable polymorphic designs.[75] The Interface Segregation Principle (ISP) advocates for creating multiple, specific interfaces tailored to client needs rather than a single, large interface, preventing classes from depending on unrelated methods.[75] This reduces coupling and compilation dependencies, allowing clients to implement only relevant behaviors.[75] The Dependency Inversion Principle (DIP) inverts traditional dependency flows by having high-level modules depend on abstractions rather than concrete implementations, often using dependency injection to decouple components.[75] It enables greater flexibility and testability, as seen in architectures like service-oriented designs.[75] Complementing SOLID, the GRASP (General Responsibility Assignment Software Patterns) provide a collection of nine guidelines for assigning responsibilities to classes during object-oriented design, focusing on responsibility-driven modeling to achieve balanced, maintainable structures. Introduced by Craig Larman in the first edition of his 1995 book Applying UML and Patterns, GRASP emphasizes rational decision-making in design to promote low coupling and high cohesion.[77] The Information Expert pattern assigns a responsibility to the class that holds the necessary information or expertise, distributing behavior to domain-relevant objects to avoid centralization.[78] The Creator pattern designates a class B to create instances of class A if B aggregates, contains, or initializes A, establishing clear ownership and initialization paths while minimizing coupling.[78] The Controller pattern uses a non-UI class, such as a facade or use-case handler, to manage system coordination and handle external requests, separating concerns from presentation layers.[78] Low Coupling and High Cohesion serve as foundational principles: low coupling reduces dependencies between classes to enhance reusability and change isolation, while high cohesion groups related elements within a class for clarity and manageability.[78] The Polymorphism pattern leverages inheritance and interfaces for behavior variation, replacing type-specific conditionals with dynamic dispatch to support extensibility.[78] Pure Fabrication introduces artificial classes to handle responsibilities that do not naturally fit domain entities, ensuring cohesion without bloating real-world models.[78] Indirection employs intermediary objects or layers to mediate interactions, decoupling primary components and improving modularity.[78] Finally, Protected Variations identifies points of instability and shields them with stable interfaces, reducing the ripple effects of changes across the system.[78] Together, SOLID and GRASP form a cohesive framework for OOP design, with SOLID targeting structural integrity and GRASP guiding responsibility allocation, both widely adopted in agile and iterative development practices.[75][77]Responsibility-Driven vs. Data-Driven Design
In object-oriented programming, responsibility-driven design (RDD) emphasizes assigning behaviors to objects based on their roles and responsibilities within the system, rather than focusing on their internal data structures. This approach models objects as service providers that fulfill contracts through interactions, using techniques like Class-Responsibility-Collaboration (CRC) cards to brainstorm and define what each class knows, does, and collaborates with.[79] CRC cards, introduced as a low-fidelity tool, facilitate collaborative design by listing responsibilities such as "maintain bit values" for a raster image object, without specifying implementation details early on.[80] By prioritizing behavioral contracts over data, RDD enhances encapsulation, reusability, and adaptability to changing requirements, making it suitable for dynamic systems where object interactions evolve.[80] In contrast, data-driven design centers on the representation and encapsulation of data within objects, deriving behaviors as operations that manipulate that data. This method adapts abstract data type principles to OOP, prompting designers to first define an object's structure—such as attributes like width, height, and bit array for an image—before outlining methods like rotation or scaling.[80] Commonly used in domain modeling, it promotes intuitive mapping of real-world entities to classes, facilitating straightforward data handling in applications like CRUD operations.[81] However, this focus can lead to tighter coupling between clients and internal structures, potentially violating encapsulation if data changes require widespread updates.[80] The trade-offs between these philosophies highlight their complementary strengths: RDD excels in behaviorally complex, dynamic environments by deferring structural decisions to maximize polymorphism and flexibility, as seen in event sourcing where objects capture state changes through immutable event sequences rather than direct data mutations.[82] Data-driven design, conversely, supports persistence-oriented systems by aligning closely with data models, such as deriving behaviors from entity attributes in relational-like mappings, though it risks scattered behaviors and lower cohesion in evolving designs.[81] For instance, a responsibility-driven image rotator might contractually promise transformation without exposing pixels, while a data-driven version defines pixel arrays upfront for efficient operations but complicates extensions.[80] Modern OOP often employs hybrid approaches that blend these paradigms, particularly in Domain-Driven Design (DDD), which integrates data-rich entities with behavior-focused aggregates to model complex domains cohesively. This synthesis addresses RDD's potential abstraction overhead and data-driven design's coupling issues, aligning with principles like single responsibility from SOLID to ensure objects balance roles and data effectively.Applications and Extensions
Object-Oriented Databases
Object-oriented databases, or object-oriented database management systems (OODBMS), extend object-oriented programming principles to data persistence by storing and retrieving data as objects rather than tabular records. In an OODBMS, objects—including their attributes, methods, and relationships—are directly persisted, preserving the structure and behavior defined in the application code. This approach allows for seamless integration with object-oriented languages, enabling developers to work with persistent objects without converting them into a different data model.[83] A key standard for OODBMS is the Object Data Management Group (ODMG) specification, particularly ODMG 3.0 released in 2000, which defines an object model supporting inheritance, where subclasses inherit properties and behaviors from superclasses, and polymorphism, allowing objects of different types to be handled uniformly through common interfaces in queries and operations. Notable examples include GemStone/S, a distributed OODBMS originally developed for Smalltalk and later supporting Java, which provides high-availability clustering for enterprise-scale persistence, and db4o, an open-source embeddable OODBMS for Java and .NET that emphasizes native object storage with query-by-example capabilities. Active modern examples include ObjectDB, a Java-based OODBMS focused on JPA compliance and efficient querying of object graphs. These systems facilitate direct storage of complex object graphs, including support for inheritance hierarchies and polymorphic queries that operate on object types without requiring explicit joins.[84][85][86][87] In contrast to relational database management systems (RDBMS), which represent data in tables and require structured query language (SQL) for access, OODBMS avoid the object-relational impedance mismatch—a conceptual gap arising from differing paradigms in data modeling, such as handling inheritance, complex types, and relationships between objects and relational schemas. This mismatch often necessitates manual mapping of objects to tables, leading to inefficiencies in development and performance; object-relational mappers (O/R mappers) like Hibernate address this by automating the conversion between object models and relational storage in Java applications, acting as a bridge while preserving encapsulation. OODBMS features include navigation through object references (pointers) for traversing relationships without query overhead, and transaction mechanisms that maintain ACID properties while respecting object encapsulation by committing changes atomically at the object level.[88][89] OODBMS find application in domains requiring complex data structures, such as computer-aided design (CAD) systems, where hierarchical object models for geometric entities and assemblies benefit from direct persistence and inheritance support to manage design versions and relationships efficiently. As of 2025, OODBMS remain a niche technology, with low overall market adoption compared to RDBMS and NoSQL alternatives, but they persist in specific enterprise contexts, particularly for Java and .NET applications handling intricate object-oriented models in sectors like telecommunications and scientific computing.[90][91]Integration with Other Paradigms
Object-oriented programming (OOP) integrates seamlessly with functional programming by incorporating principles like immutability and pure functions into object structures, enabling developers to leverage encapsulation and inheritance while minimizing side effects. In languages such as Scala, classes and traits support both paradigms, allowing objects to maintain immutable state throughval declarations[92] and pattern matching[93] for functional composition. For handling side effects in these hybrid environments, monads provide a structured way to sequence operations without mutating objects, as seen in Scala's Option and Either types[94], which wrap potentially null or erroneous values to promote safer, composable code.
Kotlin extends OOP with functional features by favoring immutable data via val properties and data classes, which encourage thread-safe, declarative designs over mutable state changes. This integration reduces bugs from unintended modifications, as data classes automatically generate immutable copies for equality checks and hashing. Similarly, F# blends OOP classes with functional immutability by default, where records and unions serve as lightweight, immutable objects that support methods while adhering to referential transparency.[95] These approaches allow developers to model domain entities as objects while applying functional transformations, yielding more predictable and testable systems.
Integration with concurrent programming addresses OOP's challenges with shared mutable state by employing models like actors, which encapsulate object behavior in isolated, message-passing units. In Akka, actors extend traditional OOP objects but process messages asynchronously, ensuring each maintains private, thread-safe state without locks or synchronization primitives that often lead to deadlocks in shared-state designs.[96] This avoids race conditions inherent in concurrent access to mutable objects, as actors serialize message handling on single threads, promoting scalability in distributed systems. However, challenges persist in coordinating actors for complex workflows, requiring careful message design to manage state transitions without introducing global synchronization.[97]
Multi-paradigm languages amplify these integrations by combining OOP structure with functional purity, enhancing code reusability and safety. Rust's traits offer OOP-like polymorphism and encapsulation through impl blocks on structs, while its ownership model enforces immutability by default[98], aligning with functional principles to prevent data races in concurrent code.[99] Swift similarly supports OOP via classes and protocols but incorporates functional elements like higher-order functions and immutable value types, enabling developers to use structs for lightweight, copy-on-write objects that blend inheritance with pure function composition. These hybrids provide the modularity of OOP alongside functional predictability, facilitating robust applications in performance-critical domains.
As of 2026, hybrid OOP-functional paradigms are increasingly adopted in AI and machine learning pipelines[100], where OOP wrappers encapsulate functional transformations for data preprocessing and model training. For instance, libraries like TensorFlow's Python API use classes to manage mutable model states while applying immutable functional operations in data flows, improving scalability in distributed ML workflows.[101]
Formal Aspects
Semantics of OOP Languages
Operational semantics provide a formal framework for describing the dynamic behavior of object-oriented programs through step-by-step execution rules that model how objects and methods interact during computation.[102] In this approach, the execution of an OOP language is defined via transition relations that specify the possible states of a program configuration, typically consisting of expressions, environments, stores, and continuations.[103] These semantics are particularly suited to capturing imperative aspects of OOP, such as mutable state and side effects, by detailing how computations proceed from one configuration to the next until termination.[102] A core element of operational semantics in OOP is the reduction semantics for message passing, which models method invocations as the primary mechanism for object communication and state modification. In reduction-based models, a program term reduces to a value through a series of one-step reductions, where sending a message to an object—such as invoking a method—triggers evaluation of the receiver to determine the appropriate method body, followed by substitution and execution with the receiver bound as the "self" parameter.[102] This process involves dynamic dispatch, where the method selected depends on the runtime type of the receiver, ensuring polymorphic behavior is resolved at execution time.[102] State transitions occur via updates to a shared store that holds object fields and method closures, allowing imperative modifications like field assignments during method execution.[103] Handling inheritance in operational semantics requires integrating class hierarchies into the evaluation rules, often by representing objects as records of methods and fields that inherit from superclasses through structural extension or delegation. For instance, when evaluating a method call on an inherited object, the semantics search the class hierarchy to locate the overriding or inherited implementation, updating the store accordingly without altering the receiver's identity.[103] This ensures that inheritance preserves behavioral compatibility while allowing state transitions to reflect overridden methods.[102] Exemplifying small-step semantics for dynamic dispatch, the imperative object calculus defines reductions such as object creation, where an object expression[l_i = ς(x_i)b_i]_{i=1}^n allocates fresh locations in the store and binds methods to closures, enabling subsequent invocations to dispatch based on the current store bindings.[102]
This rule illustrates a state transition from expression to value, with the store extended for method storage. For algebraic specification, tools like OBJ3 enable formal description of OOP constructs using order-sorted equational logic, where objects are modules with operations (methods) defined by rewrite rules, and inheritance is modeled via subsort inclusions and module extensions, providing initial algebra semantics for executable specifications.[104]
Formalizing polymorphism and recursion poses significant challenges in operational semantics, as polymorphism requires runtime resolution that complicates confluence in reduction systems, while recursion introduces potential non-termination and aliasing issues in stateful object interactions, demanding careful handling of store consistency and evaluation contexts to avoid undefined behaviors.
Type Systems and Subtyping
In object-oriented programming, type systems determine how types relate through subtyping, enabling polymorphism while maintaining safety. Nominal typing, prevalent in languages like Java and C++, defines subtyping based on explicit declarations of type names, where a type A is a subtype of B only if it is explicitly named as such, often through class inheritance hierarchies.[105] This approach enforces subtyping rules via name equality, ensuring that subtypes are declared compatible with supertypes, which supports strong encapsulation but can limit flexibility in unrelated types sharing similar structures.[106] In contrast, structural typing, used in languages like Go or TypeScript for certain features, bases subtyping on the compatibility of type structures—such as matching method signatures—without requiring explicit name declarations.[105] Subtyping rules here allow a type to be substitutable if its structure conforms to the supertype's interface, promoting duck typing where "if it walks like a duck and quacks like a duck, it is a duck," though this can introduce challenges in verifying behavioral compatibility. Behavioral subtyping extends structural and nominal approaches by ensuring that subtypes preserve the observable behavior of supertypes, formalized as the Liskov Substitution Principle (LSP). Introduced by Barbara Liskov and Jeannette Wing, LSP states that if S is a subtype of T, then objects of type S must be substitutable for objects of type T without altering the program's desirable properties, meaning subtypes must satisfy the supertype's preconditions and postconditions. This principle is formalized in terms of refinement: a subtype's methods must refine the supertype's specifications, often using pre- and postconditions to verify that method calls behave as expected under the static type.[107] For instance, in method overriding, parameter types in subtypes exhibit contravariance—allowing supertypes for inputs to broaden applicability—while return types are covariant, ensuring outputs remain compatible with the supertype's expectations. This contravariance in parameters prevents unsafe substitutions, such as passing a narrower input expectation that could violate caller assumptions.[107] Advanced type systems in OOP languages incorporate generics and compound types to enhance subtyping expressiveness. In Java, generics likeList<T> are invariant: List<Integer> is not a subtype of List<Number> despite Integer extending Number, to avoid runtime type errors from heterogeneous insertions, though bounded wildcards (e.g., List<? extends Number>) enable limited covariant subtyping.[108] Similarly, C# generics default to invariance but support variance annotations: the out keyword for covariant return types (e.g., IEnumerable<out T>) and in for contravariant parameters (e.g., IComparer<in T>), allowing safer polymorphic use in collections and delegates while preserving type safety.[109] TypeScript, layering types over JavaScript's dynamic nature, introduces union types (e.g., Fish | Bird) for alternatives that share common interfaces, enabling polymorphic dispatch on shared methods like layEggs(), and intersection types (e.g., Person & Serializable) to compose multiple interfaces into a single type for mixin-like OOP patterns.[110]
Formal verification of OOP type systems and subtyping uses theorem provers like Coq to mechanically check properties such as substitutability and behavioral refinement. The VeriJ framework, implemented in Coq, provides a modular verification environment for Java-like object-oriented programs, encoding method specifications and inheritance relations to prove type safety through inductive definitions and tactics.[111] This approach allows developers to specify subtypes with pre/postconditions and verify that overrides maintain supertype abstractions, ensuring no violations in polymorphic contexts, as demonstrated in examples verifying simple inheritance hierarchies for absence of null pointer exceptions or contract breaches.[111]