What's in a name?

27 minute read Published: 2019-06-10

Most of the programmers and almost every Java developer heard about object-oriented programming. We review code and write code based on polymorphism, encapsulation and inheritance (some of you also use absctractions). We ask or asked about SOLID, KISS and DRY during the interviews. But what's in a name?

History

You can read a history of OOP at Wikipedia, the most important thing - "object-oriented programming" term was first used by Xerox PARC and it was about "objects" - what a big surprise :-)

And starting from 1981 Smalltalk and object-oriented programming became "famous" to a wider audience, this fact leds to first OOP conference in 1986. Then, Java came into play and become the flagman of object-oriented programming.

Tiobe Programming Community Index - Java is the most popular programming language

Definition

Let's look at OOP definition in Wikipedia:

Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data, in the form of fields (often known as attributes), and code, in the form of procedures (often known as methods). A feature of objects is an object's procedures that can access and often modify the data fields of the object with which they are associated (objects have a notion of "this" or "self"). In OOP, computer programs are designed by making them out of objects that interact with one another.[1][2] OOP languages are diverse, but the most popular ones are class-based, meaning that objects are instances of classes, which also determine their types.

sounds logical and, well, formal, I think this is how we should describe OOP if asked during technical interview or educational process. It's not as formalized as Lambda calculus, but enough to describe Programming paradigm.

Overview of the various programming paradigms according to Peter Van Roy

If we can describe object-oriented programming in well-defined manner, do we really do that? I made a call for several local developers from our telegram chat and you could read their answers to question "What is object-oriented programmin?" provided below (in russian with my adapted translation into english):

RU:

Я не знаю. Наверное, на интервью я бы сказал стандартные слова про объекты и классы, три основных принципа, SOLID и паттерны проектирования. От меня ждут именно этого. :)

Корректный ответ звучит сложнее. Термин "объекто-ориентированное программирование" ввел Алан Кей [0], которого часто вспоминают в терминологических спорах, а следовательно "настоящее" ООП — это те идеи, которые развивают Кей и его коллеги. Но объекты появились в программировании раньше работ Кея, а "программирование с объектами" имеет богатую история [1]. К середине 90-х на фоне усложнения разрабатываеого программного обеспечения ООП стало ведущей парадигмой, оставив за собой оригинальный термин. Конкретные реализации (которые мы можем видеть в C++ или Java) уже не были похожи на то, над чем изначально работал Кей.

Наверное, правильно будет сказать, что ООП — это набор идей, техник и реализаций, которые образуют понятийный фреймворк (эти самые объекты-классы, три принципа и прочие солиды), помогающий управлять сложностью при разработке ПО (а так же уменьшать connascence и coupling, но это не точно). К сожалению, многие концепты ООП не имеют формализации, а их смысл неясен и является предметом споров. Несмотря на наличие теоретической базы в виде исчисления объектов [2], понимание пропонентов парадигмы (сложно) и программистов (не влияет на заработок) остается интуитивным.

EN:

Idk. I think during the interview I would say some standard words about objects and classes, three basic principles, SOLID and design patterns. That's what they want to hear from me. :-)

The correct answer sounds more complicated. "Object-oriented programming" term was invented by Alan Kay [0], and when it comes to discussions, he used as author in most cases. Based on this fact "real" OOP - ideas, which Kay and his colleagues are developing. But objects came into programming before Kay's works, "programming with objects" has a rich history [1]. In mid 90-s, during increase in complexity in software developmet, OOP became a leading paradigm and took original term with it. Certain implementation (like we see in C++ or Java) were not look like original Kay's work.

It would be right to say that OOP - is a set of ideas, techniques and implementations, which create conceptual framework (all these object-classes, three principles and other solids), helping you to manage complexity in software development (and also decrease connascence and couplin, but I'm not sure). Unforunately, a lot of OOP concepts are not formal and they meaning unclear and are subjects to negotiate. Despite theoretical basis availablity, like these [2], understanding of paradigm proponents (hard) and programmers (no matter how much they earn) is intuitive.

[0] https://en.wikipedia.org/wiki/Alan_Kay

[1] https://en.wikipedia.org/wiki/Object-oriented_programming#History

[2] http://lucacardelli.name/indexPapers.html#Objects

RU:

ооп - парадигма программирования, основанная на объектах (реализация класса). объекты могу знать, где лежат данные, определяюь поведение и всё такое. а также этот подход основан на принципах: инкапсуляция, полиморфизм, наследование, абстаргирование (?)

EN:

OOP is a programming paradigm based on objects (class implementation). objects could know, where data is allocated, define the behaviour and so on. also this approach based on principles: encapsulation, polymorphism, inheritance, abstraction (?)

RU:

Объектно Ориентированное программирование - это программирование с помощью взаимодействия друг с другом иерархий классов

EN:

Object Oriented programming - is a programming through interaction between classes hierarchies

RU:

Подход к программированию где единица абстракции это объект

EN:

Programming approach where object is absctraction unit

RU:

ООП это когда ты обращаешься с объектами как с детьми

EN:

OOP is when you treat objects like a children

RU:

ООП это когда нагородил 50 слоев абстракции и не можешь придумать название для очередного SomeShitHandlerFactoryImpl

EN:

OOP is when you heap up 50 layers of abstractions and can't figure out how to name the next SomeShitHandlerFactoryImpl

RU:

Ну эт когда ты программируешь, ориентируясь на объекты, чо ж тут непонятного

EN:

Ehm, it's when you are programming being guided by the objects, what is there to understand?

RU:

Ооп это для начала довольно объемное понятие и в первом приближении достаточно будет сказать что всё в программе объект, да какие то будут приближены к реальности (объекты типа машина, дерево и т.д), какие то не будут такими (эксепшны, ютилити объекты). Во втором приближении можно сказать как это все можно достичь, обычно посредством трёх основных понятий: инкапсуляция, полиморфизм и наследование, последние два вообще можно в одно объединить и не ебать мозги. Инкапсуляция - другие объекты не будут знать секретов других объектов. Например когда мы работаем с эррэй листом мы часто и не задумываемся что там внутри массив и что там он удваивается в определенные моменты переполнения. От этих деталей писатели этого класса нас уберегли) наследование - переиспользование кода, можно легко и просто сделать надстройку над каким то уже существующим классом, запилить свой лист на основе эррэйлиста к примеру

Полиморфизм, много форм, благодаря наследованию можем писать методы принимающие предка и всех его наследников

И получаем очень универсальный код

EN:

For start, OOP is a quite big concept and as a first approximation enough to say that all in programm is an object, yes some of them would be close to reality (objects like car, tree, etc.), some would not (exception, utility objects). As a second approximation we may say how it could be achieved, usually by three main concpets: encapsulation, polymorphism and inheritance, last two can be merged into one and don't fuck your brain. Encapsulation - other objects would not know secrets of other objects. For example when we work with array list we usually don't think about array insude and that it doubles in overflow moments. From this details authors of this class saved us) inheritance - code renaiming, ability to easy make superstructure above some exisisting class, create own list based on array lis for example

Polymorphism, many forms, thanks to inheritance we can write methods which consume parent and all it's children

And we get very universal code

RU:

Ооп очень просто, оно не смоделирует реальную жизнь, но мыслить такими объектами, такой структурой довольно просто. Инкапсуляция, да это не геттеры и не сеттеры, хотя и они тоже, как напишешь. В той форме какой их обычно пишут они конечно ниче не инкапсулируют. Наследование и полиморфизм тоже очень важные вещи. Абстракция это вообще какая то абстрактная ненужная хрень в описании того что есть ООП.

EN:

OOP is very simple, it wouldn't model real life, but think in such objects, such structure is quite simple. Encapsulation, yes it's not getters and not setters, however they are also, depends on how would write. In such a form in which they usually written in they of course encapsulate nothing. Inheritance and polymorphism are also very important things. Abstraction is a something too abstract shit in definition of what OOP is.

My thoughts

In my experience - the answer to such a question could be "learned by heart" and it's a "Three whales" option or it could be in some form of wikipedia definition, with subjective thoughts based on personal experience.

First option usually expected by the interviewers and provided by junior developers or "yesterday" students. It's easy to remember three concepts, their definitions and a couple of examples (I always cursios about "Real world examples of OOP"). But lets be honest - OOP and programming is not the "real" world. We didn't write hierarchies of apples or fruits and even if we do so, it's only one dimension - real situations are much harder to fit and categorize. Encapsulation is not a "real" thing from "car engine" example. All three whales are abstract human defined memes (imaginary entities). Also from the 2015 I guess, young specialists started to use "abstraction" (idk why, but it's funny sometimes to ask details about abstraction and its' difference from one of other princpiles, I'm trying to don't do that, but sometimes fail to hold from intention).

The second type of answers requires experience and practical understanding of "objects", "methods" and their usage in software development. It's more mature, but harder to check. It's easier for recruiter or interviewer to make a quiz with three whales than make a cognitive attitude and listen some candidate's thoughts. But if you are looking for a senior developer, if you are looking for someone who really experienced with it - you should expect this kind of answers.

You might think that I'm against such a question during the interview and didn't use it in technical screenings? Absolutely not. This is the first question I ask during the tech interview - with several small yet important "but".

  1. I make a remark: "Let's start the interview from a philosophycal question" - this should align a candidate in a less strict manner and move away from "learned by heart" answer.
  2. Then I don't require to give me a "three OOP principles", I ask for "the your personal understanding of OOP based on your experience", which makes him\her think not in academical terms, but look at this question from a more subjective point of view.
  3. And I don't expect the "right" answer - this question is a good point to start a conversation, understand candidate's way of thoughts and move on.

Things I didn't ask are "SOLID", "DRY" and "KISS" - those topics are too blurry. If you want to ask about code quality you better to talk about Connascence and Coupling with Cohesion. But sad truth is that only a small part of Java developers know about these concepts (this post aims to notify you about them and promote to read more). We should populate more formal and well-defined concepts instead of "hype". SOLID and others should not be forgotten, but it's just a step in history, not fundamental knowledge.

Sad story of a young junior and his revenge

When I was younger and failed to "learn by heart" some letters from "SOLID" it made me feel sad and put in a way of "I'm not a good developer" thoughts. Only comparison with a code of more "SOLID and DRY KISS lovers" blessed me from this curse. If someone told you that your code violates "Single Responsibility principle" - open his code, find any class where he\she placed more than two methods (which could also be treated as not "SOLID") - and have a lot of fun from trolling your reviewer by sharing your thoughts on his code until he\she would understand - that SOLID is history.

Not Alone

And I'm not alone, almost every russian speaking Java developer heard about Yegor Bugaenko and his blog\books\talks about OOP where he is trying to find the idiomatic OOP for Java. He also provides some arguments against "SOLID", "Gang of four patterns" and many other "hype" things of Java world. Thanks him - some of his early blog posts seed doubts about the Java way (espesially if you use spring + anemic domain objects + stateless services). But he is just the most "extravagant" and well known in CIS region, not the best or only one example.

I recommend you to read this blog post about the law of demeter where author said:

There are well known abstract concepts in Object-Oriented programming, like encapsulation, cohesion and coupling, that could be theoretically used to generate clear designs and good code. While these are all very important concepts, they are just not pragmatical enough to be directly useful for development. One has to interpret these concepts, and with that they become somewhat subjective and start to depend on people’s experience and knowledge.

The same can be said for other concepts like the “Single Responsibility Principle“, or the “Open/Closed Principle“, etc. These allow a very wide margin for interpretation, so the practicality, the direct usefulness is therefore diminished.

and he goes through examples of well mathematically formalized "Law of Demeter" - the objective and strong law which developers should know and use instead of "SOLID" or "DRY".

Another good materials - not mathematical, but also formal rules in great article Object Calisthenics and a very good talk (from Nicolas Frankel and thanks him for this discovery) Refactoring your legacy code for better maintainability.

There is also a good article unfortunetely on Habr and in russian about some good interview practices, about our bad habits during technical interviews and how to fix that.

Stop talking - show me the code

I spent about 150 lines of text talking about how weak definitions and non-formal principles ruin our industry, so it's time to show you some examples.

Let's start from S.

Single responsibility principle

So I "duckduckgoed" Solid in Java and here is one of the first examples of SRP:

public class Person
{
    private Long personId;
    private String firstName;
    private String lastName;
    private String age;
    private List<Account> accounts;
}
public class Account
{
    private Long guid;
    private String accountNumber;
    private String accountName;
    private String status;
    private String type;
}

And the original quote: "In given example, we have two classes Person and Account. Both have single responsibility to store their specific information. If we want to change state of Person then we do not need to modify the class Account and vice-versa." Is it OOP? For me as a "Rust" enthusiast there are "structs" not objects or classes (hello C world).

Let's find something "better" and try to "google" more examples.

Let's switch into english search. Baeldung - I bet your heard about this portal if you are Java developer.

So here is example from this site:

public class Book {

    private String name;
    private String author;
    private String text;

    //constructor, getters and setters

    // methods that directly relate to the book properties
    public String replaceWordInText(String word){
        return text.replaceAll(word, text);
    }

    public boolean isWordInText(String word){
        return text.contains(word);
    }

    // method that threaded as SRP destroyer :-)
    void printTextToConsole(){
        // our code for formatting and printing the text
    }
}

And the resolution: "This code does, however, violate the single responsibility principle we outlined earlier. To fix our mess, we should implement a separate class that is concerned only with printing our texts:"

public class BookPrinter {

    // methods for outputting text
    void printTextToConsole(String text){
        //our code for formatting and printing the text
    }

    void printTextToAnotherMedium(String text){
        // code for writing to any other location..
    }
}

I don't want to set the world on fire - but does author think that replacing words in a text is not a violation of SRP, while printing a book into the console (which every Java object could do - thanks to Object#toString()) is a violation? He thinks that BookPrinter - a stateless holder of procedures, not methods, named as BookPrinter but consuming String as a method arguments is OOP? How junior developers should understand this principle if they see such an example? How they should write a "clean" and "highly maintainable" code when we give them such examples?

Three whales

Ok, let's stop with SOLID for a second and return to the roots - OOP with polymorphism, encapsulation and inheritance. Author of our last example told that:

Awesome. Not only have we developed a class that relieves the Book of its printing duties, but we can also leverage our BookPrinter class to send our text to other media. Whether it’s email, logging, or anything else, we have a separate class dedicated to this one concern.

Total madness. I think it's a sacrafice of encapsulation to please SOLID gods with single rensponsibility principle. Instead of creating and composing objects we split our code into procedures. Let's try to understand what encapsulation is about and is it formalized enough to treat it as a good source of truth and as a measure of the "clean" code.

Encapsulation

In object oriented programming languages, encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination[1][2] thereof:

  • A language mechanism for restricting direct access to some of the object's components.[3][4]
  • A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.[5][6]

Previously I mentioned blog post about the law of Demeter, please read it if you don't.

Here I would just give an example from wikipedia:

public class Employee {
    private BigDecimal salary = new BigDecimal(50000.00);
    
    public BigDecimal getSalary() {
        return salary;
    }

    public static void main() {
        Employee e = new Employee();
        BigDecimal sal = e.getSalary();
    }
}

this is "pseudo" encapsulation. If we would use get\set prefixes - we would push our users to write things like employee.setSalary(employee.getSalary().plus(500)) instead of more encapsulated way employee.increaseSalaryBy(500). We are humans and programming paradigms are more about way of our thoughts. Extracting functionality from objects to support SRP would break encapsulation and make it "normal" way of thinking (and programming).

So while encapsulation can be used in talks about "clean" code - it's easy to cross the line between real restrictions of access and fiction.

Polymorphism

Quick wikipedia definition:

In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types[1] or the use of a single symbol to represent multiple different types.[2]

My friend and now my colleague from JS world asked me about OOP and this post is inspired by our discussion. During it I was forced to describe him - what OOP is, I made my best attempts, but I think that I failed because it's quite hard subject with a lot of opinions, meanings and popular but stupid examples on the web. First of all - I told him that OOP could not represent "real" world and it is an abstraction, meme, human imaginary term. Second - I told him about "contract". The interface, border between expected and provided.

And I tried to describe encapsulation and polymorphism as two sides of this contract using "taxi" example:

	taxi.drive(client, a, b, price); //You can see that real life examples usually not a great option 

On the one hand you as a client don't know which implementation of "taxi" type would make a ride - BMW, Lada or Tesla, you know only the client side of a contract which guarantee that it would drive you from one place to another. It's a polymorphism. And the other side of a contract - it's implementation which encapsulated from the client.

So in Java terms Taxi is an interface and interface is a contract - if you are consumer, you can use polymorphism of the contract, if you are producer, you can use ecnapsulation of the contract.

Inheritance

And the most interesting whale - inheritance.

In object-oriented programming, inheritance is the mechanism of basing an object or class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation.

Most of candidates during the questions about inheritance would try to provide you with an example from "real" world that inheritance will built up your type system of "chairs", "animals" or "fruits". But even wikipedia warns you: "Inheritance should not be confused with subtyping.[3][4] In some languages inheritance and subtyping agree,[a] whereas in others they differ; in general, subtyping establishes an is-a relationship, whereas inheritance only reuses implementation and establishes a syntactic relationship, not necessarily a semantic relationship (inheritance does not ensure behavioral subtyping)."

So if you have a hierarchy of interfaces - it's subtyping. While inheritance is just code-reuse technique (and template methods pattern is an example of such a technique usage).

SOLID

Let's back into "SOLID"

Open-closed principle

"Classes should be open for extension, but closed for modification." - what a deep thought :-)

An example from Baeldung was:

public class Guitar {

    private String make;
    private String model;
    private int volume;

    //Constructors, getters & setters
}

`We launch the application, and everyone loves it. However, after a few months, we decide the Guitar is a little bit boring and could do with an awesome flame pattern to make it look a bit more ‘rock and roll’.

At this point, it might be tempting to just open up the Guitar class and add a flame pattern – but who knows what errors that might throw up in our application.

Instead, let’s stick to the open-closed principle and simply extend our Guitar class:`


public class SuperCoolGuitarWithFlames extends Guitar {

    private String flameColor;

    //constructor, getters + setters
}

Sounds messy and it is. So if I would need to have different shapes of the guitar I should extend our Guitar class? If I want Guitar with flames and different shapes should I extend SuperCoolGuitarWithFlame or should SuperCoolGuitarWithFlame extend my SuperPuperGuitarWithShape?

That's why I prefer composition over inheritance, but let's try another popular exapmle.

Another example with struts Action classes:

public class HelloWorldAction extends Action
{
    @Override
    public ActionForward execute(ActionMapping mapping,
                                ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
                                throws Exception
    {

        //Process the request

    }
}

author thinks that by overrinding methods we could achieve better maintainablity?

Or another O\C principle example from the same article:

For example, spring framework has class DispatcherServlet. This class acts as front controller for String based web applications. To use this class, we are not required to modify this class. All we need is to pass initialization parameters and we can extend it’s functionality the way we want.

Look at this code and tell me that it's easy to extend without modification :-) Or maybe it follows singel responsibility principle?

Liskov Substitution

"If class A is a subtype of class B, then we should be able to replace B with A without disrupting the behavior of our program" - this one sounds formalized would you say. Yes - it looks like, but isn't it a subtyping? Do we really need to learn new principle to understand that every "taxi" implementation should be able to drive me from bar to home? Does the contract of an interface should have a "warning" in JavaDoc: "Please, by implementing this interface - implement it right!".

Examples from our top searched articles:

For example, every book has an ISBN number which is in always a fixed display format. You can have separate representations of ISBN in database and UI. For this requirement, we may write property editor in such a way –

public class IsbnEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (StringUtils.hasText(text)) {
            setValue(new Isbn(text.trim()));
        } else {
            setValue(null);
        }
    }

    @Override
    public String getAsText() {
        Isbn isbn = (Isbn) getValue();
        if (isbn != null) {
            return isbn.getIsbn();
        } else {
            return "";
        }
    }
}

Generally, I didn't get it - what would I see? That instead of throwing exception I use null? Baeldung example also described that if one of child classes can't implement the contract, you can't throw an exception and should rework contract.

Does code owner made wrong inheritance hierarchy of classes? Should I be an Oracle (funny in Java world) to support this principle and see the future? How to prevent even potential subclasses with "partial" implementation? Or what does it mean and to who? Parent class owner? Developers who would extend them? Behind the formal definition there is no formal meaning I guess.

Interface segregation principle

Wikipedia:

interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.[1] ISP splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them.

Wikipedia even mentions similarity of ISP with "High Cohesion" from GRASP. And for me - it sounds almost the same as SRP. Just now it's not single and we know that user is a point of view.

Okay - lets look at a popular example:

public interface BearKeeper {
    void washTheBear();
    void feedTheBear();
    void petTheBear();
}

is treated as bad in an example, while this code:

public interface BearCleaner {
    void washTheBear();
}

public interface BearFeeder {
    void feedTheBear();
}

public interface BearPetter {
    void petTheBear();
}

as a good code - this is what author said. I agree that good design should try to use only 2-3 methods in one interface (but it also depends on the situation), I don't think that it's possible and moreover really usefull in terms of software development and maintainablity to have an interface for every method. I think that really good example which could be used to describe this stupid principle is an Iterator trait in Rust standard library - which provides you with a lot of methods, but only one is required for implementation all others have default implementations and use the next method (the only you should implement).

Dependency Inversion

Wikipedia:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
  • Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

So instead of this: Traditional layers pattern you should do like this: Dependency inversion pattern

Graphical notation and principle in general are good, but they use words should, not may or could. In other words - you always should use interfaces, but as Obi-Wan said: "Only a Sith deals in absolutes". Does concept of DTOs violates this principle? Is it abstraction or implementation? I think dependency inversion is good to describe modules dependencies, but not the classes. Idea is good and obvious, it almost encapsulation but for modules and interesting fact - a lot of people miss it with Dependency Injection or even DI frameworks like "Spring" with IoC containers appoach (aka global state for your application in runtime) during the interview when they try to remember all SOLID principles.

Lev Tolstoy

So I hated a lot of popular examples, I definetely should try to provide my own for at least "three whales" of object-oriented programming.

Composition over inheritance

For those of you who heard nothing about this term - please read wikipedia page. I think that inheritance as code-reuse technique is also a history and should be replaced with composition.

Let's get an example from telecom domain:

class Order {
	private List<Task> provisioningTasks;
	private Status status = Status.INITIAL;

	void provision() {
		for (Task task : provisioningTasks) {
			task.execute();
		}
		status = Status.PROVISIONED;
	}
}

Initially we want to provision incoming orders and nothing else. New requirement told us that before we start provisioning, we should approve some orders. Let's try inheritance first.

class Order {
	private List<Task> provisioningTasks;
	private Status status = Status.INITIAL;

	void provision() {
		for (Task task : provisioningTasks) {
			task.execute();
		}
		status = Status.PROVISIONED;
	}
}

class ApprovableOrder extends Order{
	private Condition approve;

	@Override
	void provision() {
		while (!approve.isReady()) {
			wait(100000);
		}
		super.provision();
	}
}

Now what if we don't have a fully automated provisioning and we should manually turn our orders into provisioned status? It's a real case and if you would look at our code you would find a better solution, however I think I should show you inheritance approach.

class ManualOrder extends Order{
	private Condition manualProvision;

	@Override
	void provision() {
		// Our parent class should be changed to open fields access
		for (Task task : provisioningTasks) {
			task.execute();
		}
		while (!manualProvision.isReady()) {
			wait(10000);
		}
		status = Status.PROVISIONED;
	}
}

Would decorators help us in the bless of YB?

interface Order {
	void provision();
}

class SimpleOrder implements Order {
	private List<Task> provisioningTasks;
	private Status status = Status.INITIAL;

	void provision() {
		for (Task task : provisioningTasks) {
			task.execute();
		}
		status = Status.PROVISIONED;
	}
}

class ApprovableOrder implements Order{
	private Condition approve;
	private Order origin;

	void provision() {
		while (!approve.isReady()) {
			wait(100000);
		}
		origin.provision();
	}
}

class ManualOrder implements Order{
	private Condition manualProvision;
	private Order origin;

	void provision() {
		//Huston, we have a problem
	}
}

Decorators is not good, if you need to deal with something in the middle, in our case before status changed.

What you may already find - is List<Task>, can we use already composed list of tasks to insert approval check or manual status change here and leave only tasks execution in Order? The real power of composition - it makes you think in a more "clean" way. So solution would be:

class Order {
	private List<Task> provisioningTasks;
	private Status status = Status.INITIAL;

	void provision() {
		for (Task task : provisioningTasks) {
			status = task.execute();
		}
	}
}

public static void main(){
	new Order(
		Arrays.asList(
			new ApproveTask(),
			new AutomaticTask(),
			new ManualProvisionTask()
		)
	);
}

Polymorphism

And the reason why we can use several different Task implementation is polymorphism.

interface Task {
	Status execute();
}

because ApproveTask, AutomaticTask and ManualProvisionTask uses the same "contract" - we don't need to bother about their differences in "generic" Order. Order consumes List<Task> and only main() knows what implementations we want to use.

Encapsulation

And if we would receive new requirements, for example that AutomaticTask should use groovy scripts instead of python, not only Order, but in most cases direct instantiator (like main() in our example) wouldn't see any differnece. The real border of encapsulation - is compiler. We can argue that "getters\setters" is encapsulation in true meaining or not, but in reality - if you can make a call to an object property (via object.property or via object.getProperty()) this property is not encapsulated. If compiler prevents such a call (like if our property is private and don't have getter) - than it's encapsulated.

So what?

I hope agree you with all my words (or some part of it) or not - you would aks yourself are all this terms and principles we are talking about at the interviews and conferences matters? How many of them we really use in our projects and more important - how many of them are helpfull? Can we use more formal and measurable concepts to develop software in more productive way?

If you have some of this questions in your head, there are some interesting talks:

Thank you.