While true that all classes can be inherited, treating _all_ classes with the assumption that they will be would just lead to a labyrinth of code.
> While true that all classes can be inherited
That's what the 'final' keyword in C++ is for: https://en.cppreference.com/w/cpp/language/final.html
"Specifies that a virtual function cannot be overridden in a derived class, or that a class cannot be derived from."
This is particularly useful when authoring libraries as I know that for anything not public, I can refactor to my hearts content (provided the externally observed behaviour is otherwise unchanged).
It’s a bit of a pity that it was only relatively recently that Visual Studio changed the template for new classes to use internal instead of public.
There are lots of public classes in codebases I work on which are probably only public due to the old default and not used externally so could be refactored, but it’s hard to be sure.
Whereas with internal classes I can be sure (unless someone has used reflection, but then that’s their problem).
A long time ago in Java corp world, teams would create their own integration libraries with suspicious/horrible contracts. Ended up just using Java’s reflection api to update access modifiers to get their shitty library to work until they fix it (months later…).
Composition is great. It's the preferred way.
However, if you take it to the extreme (composition only, never inheritance) you sometimes end up with really weird meaningless objects that have only a single method and a one-to-one relationship with another object.
Private methods are ideal for making those abstractions easier to understand. A private method, therefore, is equivalent to an object that has a one-to-one relationship with another object (it's only composed there) and a single method.
A protected method is similar, but allows whoever subclasses it to replace that single one-to-one "mini object" inside with another one-to-one "mini object".
When used within this mindset, it greatly simplifies code. It lets you simplify a branch that has only a single leaf into something easier to read.
Together with classical refactorings (extract method, move method, extract class), they're a precious tool.
Of course, typing is a consideration as well, but I'm talking exclusively about their role in granularity control.
It is unecessary, but convenient. It can be misused. Composition can also be misused.
We can take a cue from the functional languages and make this less code-smelly by using anonymous function types (e.g. lambdas). This removes the one issue with single-purpose/single-method objects, which is naming them.
There is no silver bullet. No one likes huge inheritance chains, no one likes only tiny lego pieces, no one likes callback hell.
No thanks. And I'll continue using inheritance and composition correctly too.
Otherwise, at least in C++, you can often bypass the private specifier without much difficulty. Perhaps the laziest and easiest way to do so is
#define private public
#include "foo.hpp"
#undef private
But even if that were true, that wouldn't mean that accessibility modifiers are unnecessary in other languages. Python is a certain language with certain idioms, and other languages are more or less OOP and hence some constructs that might not make sense in Python definitely make sense elsewhere.
Less work in Python than it is Java, but there are ways to get to them in Java, or hell, just modify the source and change it directly. By doing that, you're doing something the developer didn't intend and you shouldn't be surprised if it doesn't work at some point in the future. Same as I'd expect in the Python case.
https://www.cppreference.com/w/cpp/language/friend.html
Redefining public as private can violate the ODR rule and triggers undefined behaviour because private members are ordered in memory in an undefined way, while public members are ordered in a standard way.
class Foo {
public:
void method_A();
protected:
friend class Bar;
void method_B();
protected:
friend class Baz;
void method_C();
private:
void method_D();
};
anyone can call method_A(), only Bar can call method_B(), only Baz can call method_C() and nobody can call method_D().You can do this by inheriting from 2 different pure abstract classes, but that feels much kludgier to me.
Languages like Python don't have keywords but enforce using conventions like underscore and 'dunder', the latter of which is actually enforced using obfuscation. It is extremely helpful to signal to users that they should not be using certain methods or fields. When there are no access modifiers available, we still see teams writing methods like "xyx_UNSAFE" or "abc_DO_NOT_USE". It's ridiculous to me to encode this kind of information like this instead of having first class support in the language.
Access modifiers also tell the compiler or JIT what is safe or unsafe to manipulate. It has implications beyond users, and can be a determining factor when inlining or making other optimizations.
This article is so opinionated (edgy?) that it ends with "Inheritance was a hack in the first place." I think, like all things, we need to consider the broader landscape for object oriented programming instead of just saying these things are irrelevant and absurd and useless.
See also https://steve-yegge.blogspot.com/2010/07/wikileaks-to-leak-5...
And if you don't need private then why have public?
The usefulness of public/protected/private is that it provides a way to narrow the surface area through which derived classes can interact with their superclasses. Which is enormously useful from a maintenance perspective.
The place where inheritance is better than composition is where base classes provide partial implementations that are used by derived classes.
Dwedit•5h ago
scotty79•5h ago
It should at most be a soft annotation to prevent it from getting auto-completed, not a restriction.
tenebrisalietum•5h ago
scotty79•1h ago
I can't imagine somebody monkey patching exposing internals of a lib and blaming lib author for poor effects.
magicalhippo•4h ago
Then the internals are accessible if you need it for a temporary workaround or such, but it's also very obvious when you are accessing stuff you really shouldn't. And implementers are still free to rearrange the internals as they see fit.
[1]: Not saying Boost invented it, just where I saw it first.
rerdavies•2h ago
derefr•4h ago
• There are also runtimes, installed separately from the application. You might be able to fork the JDK, but people want to run your Java application on their JRE, not yours.
• There are proprietary applications that nevertheless expose APIs to code against. A Photoshop plugin author can't fork Photoshop to expose fields of the plugin host.
• There is the OS itself. You can fork the Linux kernel (and gcc/clang) to expose some internal data your application needs as a system call — but unless you get all that code upstreamed, your application is now entirely non-portable, running only on your own servers at best.
• Most commonly of all, there are system libraries. You can certainly fork (and co-distribute / bake in) a library like libicu or libxml — but no major OS will be willing to ship your app or lib as a system package of its own, if it does that. They'll want your app/lib to depend on the existing packaged version of those libs.
None of this matters, of course, if you develop proprietary-service backend software for a living. You can maintain a menagerie of weird lib forks, shove all the build artifacts into a docker image, and it'll work for you. But much (I'd say the majority) of the world's programming is not in proprietary-service backends, and so needs to actually depend on code it doesn't control.
scotty79•1h ago