-
Based on the idea of "messaging" between objects, VERY poorly named "object oriented programming", admitted by Alan Kay himself, the inventor of the term.
- Alan Kay at OOPSLA 1997 - The computer revolution hasn't happened yet
- The big idea is “messaging.”
- The key in making great and growable systems is much more to have things communicate through messages and not through direct mutable state.
-
BOOP style takes the "message" metaphor to mean that the state of the program is immutable except by sending messages to program objects in order to create a new state.
-
The
object
state can only changed by creating a new state via modifying the old state by callingpublic
methods on objects. -
The state of the
object
isprivate
and only accessible via methods that are called on the object.-
Alan Kay, 2015: Power of Simplicity
-
Rethinking CS Education | Alan Kay, CrossRoads 2015
-
We Were So Lucky - Alan Kay
-
Seminar with Alan Kay on Object Oriented Programming (VPRI 0246)
-
-
-
All state is fully retained in the
object
and methods are used to "send messages" to theobject
to change its state. This "messaging" metaphor stands up because internal state is not directly accessible. You must politely ask the object to change its state, and the object can choose to respond to the message or not. Unlike in COP where internal state is routinely exposed and directly manipulated viastatic
methods and variables. -
State of the object is exposed only via "messages" (method calls to the objects
public
methods,) and the object can choose to respond to the message or not. -
No
static
methods or variables, onlyprivate
instance variables inside theobject
.- Use of
static
methods and variables is specifically disallowed as it leads to dreaded "shared mutable state" and "side effects" that are difficult to maintain and understand as program complexity grows.Objects vs. Static Methods - Yegor Bugayenko
- Use of
-
protected
methods are allowed, but discouraged as they lead to "fragile" and "rigid" code. -
Use of inheritance is explicitly discouraged, and prefer composition over inheritance.
- Exceptions for shallow hierarchies of 2-3 levels maximum meant to model the real world objects, not to create a "hierarchy of types to
extend
functionality."
- Exceptions for shallow hierarchies of 2-3 levels maximum meant to model the real world objects, not to create a "hierarchy of types to
-
Use of
interface
-s is limited toclass
-es that require individual testing, and are not automatically added for everyclass
without thinking. -
Use of
set
-ters andget
-ters is specifically disallowed. Instead, the object is expected to respond to messages to change its state, and to respond to messages to reveal (transfer) its state. -
There are only a few true BOOP languages, "Smalltalk," "Ruby," and, incredibly, "Javascript" are among the most popular ones.
🚨
⚠️ ‼️ A Word Of Caution: BOOP patterns may get you in trouble with your boss, as the patterns are not widely known, understood or accepted.
- Although these patterns are slowly seeping into some areas of software development, like "Compose" from JetBrains, these ideas are not known or understood by most software developers, and very few libraries use BOOP patterns.
“Recently I have seen the first part of your lecture called “Pain of OOP” and was very intrigued by the average age of the visitors. How do you think, will it be hard for them to find a job when the courses are over? Won’t that leave a “footprint” in their minds that almost everything they will see as junior software engineers will be totally against what you taught them? Or do they have to accept that as it is, taking into account that, as juniors, they will have no right to even propose changes to the architecture?”
- Here is the blog post cautioning you about introducing these patterns (EO is Yegor's term for BOOP):
-
- Yegor Bugayenko, Author of Elegant Objects
- BOOP is my name for the style of programming that Yegor Bugayenko describes in his book "Elegant Objects."
- What's Wrong With Object-Oriented Programming? Yegor Bugayenko
-
-
Factory Pattern.
-
Abstract Factory Pattern.
-
Adapter Pattern is OK in when used as encapsulation, as BOOP prefers composition over inheritance.
-
Facade, Bridge, Proxy and Decorator Patterns are all very similar to the Adapter Pattern and conditionally accepted in BOOP.
-
Builder Pattern considered too clumsy, the "Fluent" pattern is preferred:
- "Fluent Pattern" is where an
object
is modified and returned to the caller to allow for "method chaining." - Builder pattern is discouraged since it encourages us to create and use big, complex objects.
- If you need a builder, there is already something wrong in your code.
- Refactor it so any object is easy to create through its constructors.
- "Default parameters" make builders obsolete in Kotlin.
- "Fluent Pattern" is where an
-
"Marker" interfaces are discouraged.
- An interface with no methods, like
Serializable
orCloneable
, is called a "marker" interface. - Useful for system & library level notations, but not use in application level code.
- An interface with no methods, like
-
Type-casting is frowned upon, and prefer the use of interfaces or composition as type-casting was only necessary for COP style implementations.
- 10 Design Patterns Explained in 10 Minutes
- Patterns, Anti-Patterns, and Refactoring - Yegor Bugayenko
-
-
- Use of
null
-s to define the state of the object. - Use of
static
methods and variables. Get
-ters andSet
-ters.- Reflection & Type Casting.
- Inheritance more than 2-3 levels deep. Prefer no inheritance and use composition instead.
- Mutable State - All state in BOOP is immutable, and the object is expected to return a new object with the new state.
- Classes ending with -er that mutate data passed in without retaining state internal to the object.
- Like
Manager
,Controller
,Handler
,Processor
,Updater
,Setter
,Getter
,Modifier
,Changer
, etc.
- Like
- Use of
-
Amazingly, the dreaded Singleton Pattern is allowed to manage global immutable state in BOOP, but no
static
variables, functions or methods.
-
The main problem with COP is that it's common practices lead to fragile code that was difficult to maintain and understand, for similar reasons as to why "Spaghetti Code" is difficult to maintain and understand.
The Pain of OOP, Lecture #3: Getters and naked data - Yegor Bugayenko
The Pain of OOP, Lecture #5: -ER Suffix is Evil - Yegor Bugayenko
-
Example of BOOP in Kotlin:
flowchart LR subgraph Application subgraph Book subgraph Pages A("📄 Page 1 Content")-->|enclosed in| D B("📄 Page 2 Content") -->|enclosed in| D C("📄 Page 3 Content") -->|enclosed in| D end D("📑 List of Pages") -->|enclosed in| E(Book) end E("📖 Book") -->|enclosed in| F("🖥️ Application") end click F "https://github.com/realityexpander/How_to_program_from_ground_up/blob/main/src/main/kotlin/boopExample.kt" _blank
-
class Page( // <-- the Page class constructor private val content: String // <-- the "val" keyword means the variable is immutable and can only be assigned once. ) { fun view() { println("Page: ${inspectContent()}") } fun updateContent(newContent: String): Page { return Page(newContent) // <-- the "updateContent" method is expected to return a new object with the new state. } fun inspectContent(): String { // <-- allows access to the private state of the object, only via a method call. return content } } class Book( val title: String, private val pages: List<Page> ) { fun view() { println("Book: $title, # of Pages: ${pages.size}") pages.forEach { it.view() } } fun updateTitle(newTitle: String): Book { return Book(newTitle, pages) // <-- The "updateTitle" method returns a new object with the new state. } fun updatePages(newPages: List<Page>): Book { return Book(title, newPages) // <-- The "updatePages" method returns a new object with the new state. } } class Application( private val book: Book // <-- The "Application" class, the "val" keyword means the variable is immutable. ) { fun view() { println("Application Viewing: ${book.title}") book.view() } fun updateBook(newBook: Book): Application { return Application(newBook) // <-- The "updateBook" method returns a new object with the new state. } } // Start of Program fun main() { // Setup the App in the familiar Imperative Style: // Create the list of Page objects val pages = listOf( // <-- the "val" keyword means the variable is immutable and can be assigned only once. Page("Page 1 Content"), Page("Page 2 Content"), Page("Page 3 Content") ) // Create the book object using the list page objects val book = Book( "MyBook.txt", pages ) // Create the application object using the book object var app = Application(book) // <-- The "var" keyword means the variable is mutable, // `app` is a "var" because it's expected to change state. // `app` is using the Singleton pattern, and it's allowed in BOOP, as its // state is immutable. app.view() // <-- will print: // Application Viewing: MyBook.txt // Book: MyBook.txt, # of Pages: 3 // Page: Page 1 Content // Page: Page 2 Content // Page: Page 3 Content // The above code could be arranged in the functional style, where the state of the program is created in // a single line! // // - This style is also known as "declarative" style, as opposed to the familiar "imperative" style. // - Using declaritive style, the code is about "what" needs to be done, rather than step-by-step "how" to do it. // - As a programmer, you only see the high-level view, as the implementation details are hidden deeper // in the code, "abstracted" away in the functions. // - Functions are called and executed from the innermost brackets first to the outermost assignment last. // - The state of the program is created in a single call to the `Application` constructor. // - This style is also called "composition." // Setup the App in Functional Style: // The code executes from the innermost function to the outermost function (step 1 to step 5.) app = Application( // <-- Step 5 Creates a new Application object with the book object. Book( // <-- Step 4 Creates a new Book object with the title = "MyBook.txt", pages = listOf( // <-- creates a new list of Page objects with the content "Page 1 Content", "Page 2 Content", "Page 3 Content" Page("Page 1 Content"), // <-- Step 1 Creates a new Page object with the content "Page 1 Content" Page("Page 2 Content"), // <-- Step 2 Creates a new Page object with the content "Page 2 Content" Page("Page 3 Content") // <-- Step 3 Creates a new Page object with the content "Page 3 Content" ) ) ) app.view() // <-- will print the same as the imperative style: // Application Viewing: MyBook.txtimp // Book: MyBook.txt, # of Pages: 3 // Page: Page 1 Content // Page: Page 2 Content // Page: Page 3 Content //////////////////////////////////////////// // Changing the State of the Application // //////////////////////////////////////////// // app.book = Book("UpdatedBook.txt", emptyList()) // <-- will not compile, as the variable `book` is immutable // and cannot be modified. It can only be replaced. // To change the state of the application, a whole new object must be created with the new state, // usually based on a copy the old state, with modifications to reflect the new state. val newPages = pages .filter { page -> // instead of using imperative "for" loops, "filter" internally uses a loop to create // a new list of pages. page.inspectContent() != "Page 2 Content" // <-- removes the 2nd page from the list. } .toMutableList() // <-- converts the immutable list to a mutable list to allow for adding a new page. .apply { // <-- creates a new list of pages with the same content as the original list, but with // the 2nd page removed. add( // <-- adds a new page to the list. Page("New Page 4 Content") // <-- creates a new page with the content "New Page 4 Content" ) } .toList() // <-- converts the mutable list back to an immutable list. // The `updateBook` method is called to update the `book` which will create a `app` with the new state. app = app.updateBook( app.book // <-- Using the `book` from the current state of the application to copy the state of the `book`. .updateTitle("UpdatedBook.txt") // <-- Creates a new book with the updated name and the same `pages`. .updatePages(newPages) // <-- Creates a new book with the updated `pages` and the same `title`. ) app.view() // <-- will print: // Application Viewing: UpdatedBook.txt // Book: UpdatedBook.txt, # of Pages: 3 // Page: Page 1 Content // Page: Page 3 Content // Page: New Page 4 Content } // Output: // Application Viewing: MyBook.txt // Book: MyDocument.txt, # of Pages: 3 // Page: Page 1 Content // Page: Page 2 Content // Page: Page 3 Content // Application Viewing: UpdatedBook.txt // Book: UpdatedBook.txt, # of Pages: 3 // Page: Page 1 Content // Page: Page 3 Content // Page: New Page 4 Content
Live Code Example: BOOP example
BOOP Ktor Back-end code style guide: Ktor Web Server BOOP Code Style Guide
BOOP KMP code style guide for cross-platform Kotlin Mobile App "Fred's Roadtrip Storyteller": KMP App BOOP Code Style Guide
-
Software design is a human activity and reveals the errrors in conceptualizing our representation of the problem.
-
There are common patterns to the errors, which lead to problems implementing the solution, and maintaining it easily.
- A system is not a tree - Kevlin Henney
- The Error of Our Ways • Kevlin Henney • GOTO 2016
- CppCon 2019: Kate Gregory “Naming is Hard: Let's Do Better”