Just saying "if you are really smart you'll see that oop is bad" just sounds so... Empty.
Just saying "if you are really smart you'll see that oop is bad" just sounds so... Empty.
* "Everything is an object". This creates unnecessary confusion between data and modules and sometimes even functions.
* "Everything is a message". This encourages splitting state between a myriad of objects, each hiding some tiny bit of mutable state that changes whenever it receives a message. Reasoning about such a state graph is impossible for any non-trivial application.
* "Everything is an interface", which leads to tests tightly coupled to implementation due to mock over-usage.
* "Everything is an inheritance hierarchy". Practice has proven that composition is better than inheritance.
Everything is a pointer-wide cell of bits, with a type tag.
Now we're cooking with gas!
Traits feel more natural to me. Instead of needing to make a subclass of a data structure to attach a foo method to it, you can define a collection of foo methods with the same name and pick the runtime implementation based on data structures passed to the method. I understand that this six of one, half dozen of the other, but I do prefer it this way.
Granted, this is not something OOP does "wrong," and you can use generics to accomplish a similar thing, so it's not that big of a deal.
I fully accept that not everything benefits from being expressed in objects, which is why hybrid languages like Python are so excellent. But yes, even if some things don't map, keeping OOP around for things that do makes too much sense to ignore.
It used to be GUIs until React, Elm and such proved that FP style describing the end state is a better fit.
"Simulations" can be done in both real time state management OOP style or FP or data centric style with explicit time.
Operating systems are a good example for state management OOP. Linux is written in C with OOP techniques. FP with immutabilty of objects would likely not be a good fit because operating systems are expected to just run in thr background and to not waste resources (eg by copying full objects each microsecond).
Anything where Erlang shines is another good fit for OOP because its actor model actually is the original vision of OOP - objects reacting to messages.
OOP only provides illusions of reuse-ability. In reality every component created in OOP takes extra steps and extra glue code to make compose-able.
In programming you want to build small primitives that you can compose to form larger higher order primitives. This allows you to reuse lower level components to form new programs. OOP does not allow you to do this easily.
An object is a grouping of primitives. So according to your logic OOP should have the same problem.
The real issue isn't grouping. The true nature of abstractions is the formation of higher level abstractions from "groupings" of lower level primitives. So "grouping" is fundamental to abstraction and therefore fundamental to programming.
The problem with OOP is that the groupings are forced. I cannot reuse a method outside of the context of the object because the method is tied to mutable state.
For example:
Try to write a function addOneTimesTwo from the composition of the three primitives below (in python):
def addOne(number):
return number + 1
def timesTwo(number):
return number * 2
def compose(f, g):
return lambda x: f(g(x))
Answer:
addOneTimesTwo = compose(timesTwo, addOne)You will note, compose is a universal functional operator. The definition for compose above is the most primitive way to compose functions in FP.
Try to do the same with OOP:
class AdditionObject:
def __init__(self, number):
self.number = number
def addOne(self):
self.number += 1
class MultiplicationObject:
def __init__(self, a, b):
self.a = a
self.b = b
def mult(self):
return self.a * self.b
Certainly do-able with OOP. But I cannot extract the methods away from the state. In order to use the banana (method) I need the gorilla holding it and the entire forest it lives in (Object) to come with it.It can be done, but generally you need a lot more glue code to make it work especially given the fact that people can get more creative with OOP. As you can see the MultiplicationObject is an example of this creativity. It was written with a different interface then the AdditionObject.
Basically if you know haskell even similar issues to the above can be dealt with in terms of haskell. You can certainly define a composition operator for even the most complex types. The issue with OOP is that EVERYTHING is a complex type. A standard Object already has enough complexity to necessitate custom composition operators so you're basically writing custom glue code everywhere. Either you're writing complex glue code or your bending all your code to fit into a framework.
Nothing is truly modular.
class Math {
public static function addOne (int $i){ return $i+1; }
public static function multiTwo function (int $i){ return $i*2; }
}
With classes those functions or methods are grouped together under a math class.Calling it is as simple as: Math::addOne(2)
When you need Math you include it.
Seem cleaner than including little addOne functions as global closures.
I'm the programmer that has to compose these classes... You're telling me that I have to change the implementation/interface of what they wrote? What about the dependencies?
This is what I mean by "Not reusable."
Additionally typical OOP involves mutating state. The purpose of the class is to hold methods that operate on internal state. Otherwise what you're writing is basically function calls under a namespace.
>Seem cleaner than including little addOne functions as global closures.
Again Put it under a namespace called Math; problem solved. The purpose of a namespace is to group functions together. The purpose of a class is to group methods together that operate on shared state.
If your methods don't involve a "this"
function addOne(){
this.value + 1;
}
then you're not really doing OOP. You're just repurposing the class as a namespace.The great thing about repurposing a class as a namespace is I can pass that repurposed namespace package anywhere.
You never need to change their code either inject those objects into a new class or extend one and inject the other.
namespaces can be passed anywhere anyway.
>The number + 1 or x 1 doesn't require an object or oop. It doesn't require a function either and would be better to just inline.
+ 1 and x 1 are just examples. If you take the examples and make it more complex the problem still remains.
An inlined expression is still a function. It's just not named.
y = function(x){return x+1;
b1 = 1 + y(2)
x = 2
b2 = 1 + (x + 1)
In the above code y(2) and x = 2; x+1; are equivalent.>You never need to change their code either inject those objects into a new class or extend one and inject the other.
You rarely ever need to do this in FP, but you need to do this all the time in OOP.
ORMs that overload getters and setters as DB transactions: It creates a harmful illusion that the data inside the object are equally as authoritative as the data in the source of truth (as in database).
See https://twitter.com/dpc_pw/status/1240719977071575040 for how this works in practice.
People like to call this a leaky abstraction but I think it's far more boring. Know your tools (both the ORM and the database). It will be worth it. Don't hesitate to use raw SQL if the ORM doesn't play along.