Skip to content

Latest commit

 

History

History
311 lines (270 loc) · 17.9 KB

11-BackToObjectOrientedProgramming.md

File metadata and controls

311 lines (270 loc) · 17.9 KB

BOOP Style (Back-to Object Oriented Programming) 1980s-Present

Can we re-visit the original principles of OOP to make software easier to understand and maintain?

  • BIG IDEA #1 — The state of the program is immutable and is only changed by creating a totally new state by modifying the old state.

  • BIG IDEA #2 - The state of the objects is private and only mutable via methods that are called on the object.

Alan Kay Style OOP (The Original Object Oriented Programming)

  • Based on the idea of "messaging" between objects, VERY poorly named "object oriented programming", admitted by Alan Kay himself, the inventor of the term.

    "I'm sorry."—Alan Kay

    Alan Kay:

    • "I'm sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea."

    • 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.

  • All state is fully retained in the object and methods are used to "send messages" to the object 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 via static 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, only private instance variables inside the object.

    • 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

  • 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."
  • Use of interface-s is limited to class-es that require individual testing, and are not automatically added for every class without thinking.

  • Use of set-ters and get-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.

boop-caution

🚨⚠️‼️ 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.

A quote from Yegor's Blog Post from a fellow developer about Yehor's 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?”

Banned COP Patterns in BOOP

  • banned-patterns-in-boop

  • Specific COP-like "Design Patterns" are NOT permitted in BOOP, such as:

    • 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.
    • "Marker" interfaces are discouraged.

      • An interface with no methods, like Serializable or Cloneable, is called a "marker" interface.
      • Useful for system & library level notations, but not use in application level code.
    • Type-casting is frowned upon, and prefer the use of interfaces or composition as type-casting was only necessary for COP style implementations.

  • Other Common COP Patterns that are Disallowed in BOOP:

    • Use of null-s to define the state of the object.
    • Use of static methods and variables.
    • Get-ters and Set-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.
  • Amazingly, the dreaded Singleton Pattern is allowed to manage global immutable state in BOOP, but no static variables, functions or methods.

Why is Class-Oriented Programming (COP) Bad?

  • why-is-cop-bad
  • 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
    
    
  • Back-to Object Oriented Programming Example (Kotlin):

  • boop-example
    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

More On Software Design