As far as your email example, sometimes the language's basic type system abstractions (e.g., string, integer, etc.) do not encapsulate a real-world concept sufficiently. Encapsulation failures can frequently lead to bugs when edge cases are hit. I may, indeed, need to create an Address class to replace "string address" parameters if there is validation logic inherent to the existence of an email address in my code. If there isn't, then the base type of string would do just fine, of course.
Both patterns are total failures. You are talking about the most misguided religiously followed pattern(s) in all of modern programming. AN equal amount has been written about how horrible both DI concepts are if you care to look. You think putting an acronym in your response and using big words like injection proves a point? Not SOLID at all. It proves nothing.
You have literally ignored my main point. It baffles me how blind some people are. The Email object requires 10x more code and 10x more abstractions and 10x more unit test coverage to finish then a singular function. You haven't even addressed this fact yet.
>As far as your email example, sometimes the language's basic type system abstractions (e.g., string, integer, etc.) do not encapsulate a real-world concept sufficiently. Encapsulation failures can frequently lead to bugs when edge cases are hit. I may, indeed, need to create an Address class to replace "string address" parameters if there is validation logic inherent to the existence of an email address in my code. If there isn't, then the base type of string would do just fine, of course.
Do you seriously think that encapsulating an email address in an object makes a difference? Here's some "validation logic" for you:
//raises error if invalid
validate_email_address(email: str)
I cannot make an example more clear. If you want to send a VALID email all you need is a function that validates an email and a function that sends it. You do not need to shove validation into an extra object then inject it into another one, then instantiate that object then call the send method... Doing this adds NOTHING beneficial to your code.This one guy told me that by encapsulating the email address into an Address object I am effectively eliminating a runtime exception from the system because the type checker is, in a way, doing the validation. haha. I cannot comprehend how this guy cannot visualize the fact that all he is doing by using an address object rather then a validation function he is just creating a class then basically moving the validation function over to be a method of that class. The runtime error can still occur... just in a different location and obscured by useless abstractions and code surrounding it.
Many OOP patterns are pointless abstractions. Many smart people hate OOP all together, (including the founder of YC and this site). Maybe you should look into why.
You are basically the topic of this article.
One point of a type such as EmailAddress is that the validation is invoked at one place (inside the constructor) as opposed to an invocation at every usage site like send_email, which is a lot of work and error prone.
Another point is that when validation fails you get the runtime exception with a useful stack trace: instead of "address X was not valid when trying to send email" you actually get "you tried to convert string X to email address but it's not valid". In the first case all you can do is fail to send the message, but in the second case you can ask the user again, fix your escaping code etc.
That is not true. Assuming that both have same functionality, e.g. both either validate address correctness or both don't, you need exactly same test cases. The only difference is that they will be structured differently - in one case you validate address as part of `AddressObject` test and in another as part of `func email` or `func validate_email_address`.
The tests with valid and invalid address however needs to exist in both cases.
Whether you go by functional or object oriented way, validation should be separated from main. It can be in another object or in another function, but it definitely does not belong to main function that does also sending. There is very little difference between two, the only one is that whether you validate before hitting send function of after.
> You do not need to shove validation into an extra object then inject it into another one. Doing this adds NOTHING beneficial to your code.
It is not big deal either. It does not make it harder to understand, both variants are dead simple. Both are immediately readable. Frankly, people who have low abstract thinking or were lazy to learn basics of OOP hate it. It is the same as with people who hate any other paradigm - functional programming and what have you. Functional programming is too hard to read unless you have put work into learning it.
There is such a thing as bad OOP, but when you pick dead simple easy to read example and claim it is something complicated, the the issue is not with OOP.
Typically when you make a class. You create additional methods under that class. Those additional methods such as getters, setters, constructors, all need to be unit tested for full coverage. Like I said... Cruft code.
>Whether you go by functional or object oriented way, validation should be separated from main. It can be in another object or in another function, but it definitely does not belong to main function that does also sending. There is very little difference between two, the only one is that whether you validate before hitting send function of after.
We're in agreement here except I never brought up functional programming. Where in my post did I say it? I'm talking about procedural programming. Like C.
>It is not big deal either. It does not make it harder to understand, both variants are dead simple. Both are immediately readable. Frankly, people who have low abstract thinking or were lazy to learn basics of OOP hate it. It is the same as with people who hate any other paradigm - functional programming and what have you. Functional programming is too hard to read unless you have put work into learning it.
This is besides the point. I don't care about how hard or how simple or how lazy people are. I'm talking about pointless logic and unneeded abstractions. This is what the article is talking about. Here is a way to make it even more clear. Compare the two below:
Class Email(AddressObject, SubjectObject, MessageObject, ConfigObject, ClientObject).send_mail(msg: str, subject: str, address: str)
func send_mail(msg: str, subject: str, address: str)
Do you see how the send_mail method is IDENTICAL to the send_mail function? The class and the dependencies getting injected into the class are a pointless layer of abstractions when ALL you need to send an email is the send_mail method. This is the core of the argument. Don't get into semantics about people being lazy.And also, again, I never mentioned functional programming. I'm not talking about it. I'm talking about straight up procedural programs of just regular functions that you see often in C code.
>There is such a thing as bad OOP, but when you pick dead simple easy to read example and claim it is something complicated, the the issue is not with OOP.
I picked something dead simple and easy so readers can understand what I'm talking about. You'd rather me hand you some OO source code so you can parse that insanity?
That is great argument for why full coverage is nonsense. Why would you test getters, setters and constructors if they do little?
> Compare the two below
I read and understand both just fine. Moreover, second one must have a place where equivalent of config and client objects go - but I dont see where it is. Or, you added do-nothing config and client objects go to OOP example to make it look worst. OOP example also have address twice, subject twice and message twice.
Here is more equivalent code:
* Class Email(AddressObject, SubjectObject, MessageObject).send_mail()
* func send_mail(msg: str, subject: str, address: str)
> I picked something dead simple and easy so readers can understand what I'm talking about.
The trouble is, that readers dont see it.
The point of functional was analogy.
Errors and code changes can happen at anytime and anywhere and in places you never look. You have full coverage for full safety.
>I read and understand both just fine. Moreover, second one must have a place where equivalent of config and client objects go - but I dont see where it is. Or, you added do-nothing config and client objects go to OOP example to make it look worst. OOP example also have address twice, subject twice and message twice.
The function doesn't need a config object or a client object because those are patterns and abstractions that are unnecessary. That is the point of the article. Patterns that are blindly followed religiously and abstractions used in a way that is excessive.
>The trouble is, that readers dont see it.
It's not possible to speak about what other people see. You can only speak to your own intelligence which is the only logical possibility here.
>The point of functional was analogy.
The point was that you think functional and oop are just two different styles of programming that are both equal and good. This is incorrect. One is better than the other but at the end of the day I wasn't even comparing those two styles.
It is not even about abstraction. I can write func send_mail(msg: str, subject: str, address: str, from: str, server: str, port: num, <following 20 config variables>) and have procedural code with same functionality.
Writing test for every getter and setter will not give you safety at all. This sort of test is completely pointless.
validate_email(email: str, subject: str, msg) -> bool
return validate_msg(msg) && validate_subject(subject: str)
This makes it hard to "unit test" as hard core unit testing purists like to test the email function decoupled from other dependencies such as validation. The reality is, it's not that big of a deal to test both functions at once in a single unit test. You can't prove "unit_tests" is any better than regular "tests"And if you really want to do DI you can always do this:
email(address: str, subject: str, message: str, address_validator_function: func)
Though travelling in this direction we're diving deeper into another type of pattern that can be abused. Still, passing functions as parameters still leads to less cruft code then straight up passing a method that's part of an object.For example I could have the only way to construct an email address be (in Haskell)
makeEmailAdress : String -> Maybe EmailAddress
With the validation logic in the constructor. This is no more complicated than your solution.As an added benefit my type signatures are now also doubling as documentation.
You are correct in that building types leads to a more safe program but I was more addressing the flaw with objects rather then types.
Leading to an OO class design roughly like this: (there are other possible OO designs, but this illustrates the point)
// Send an email to a specific user or a group of user taking into account both user and group level filters.
sendEmail(Email, EmailDestination)
class Email {Title, Body, Attachments}
interface EmailDestination {
Iterator<User> getRecipients(Email);
List<Filter> getFilters();
}class Group implements EmailDestination {
// returns group filters
List<Filters> getFilters();
List<User> getMembers();
// returns all recipients after applying group and
// individual recipient filters
List<User> getRecipients(Email);
}class User implements EmailDestination {
// returns user specific filters.
List<Filters> getFilters();
// returns itself unless filtered
Iterator<User> getRecipients(Email);
}how would this look with a procedural design?
User : NamedTuple[user: str, email: str, group: str]
func anExampleFilterFunction(any_Tuple_including_User: NamedTuple[str,...]) -> bool
func filterByAFunction(iterable: List[NamedTuple[str,...]], filter_func: func) -> List[NamedTuple[str,...]]
func getValuesFromTupleByName(name: str, List[NamedTuple[str,..]]) -> List[str]
func sendEmails(email_list: List[str], msg: str, subject: str)
Less lines, less cruft, more modular and No objects.A bit off topic but note that... anExampleFilterFunction can be implemented to filter off of a User and a group at the same time or it can filter off of the email address itself. I could also compose filterByAFunction with itself in an innumerable amount of ways to produce any amount of custom filters:
//a, b, c are different filter functions.
filteredUsers: User = filterByAFunction(filterByAFunction(filterByAFunction(x, a), b), c)
//or
filterdUsers: User = filterByAFunction(x, func(y){ return a(y) && b(y) && c(y)})
The tricks(s) above cannot be done if I had everything encapsulated in objects. Objects are not compose-able. You cannot combine User or Group to create a UserAndGroup filter without explicitly defining a whole new class. Also in the end all a program really is doing is calling functions. Classes are just layers of cruft that have no meaning during execution.Plus even with it not meeting the requirements, it is confusingly presented, and is filled with terminology related to the implementation rather than the domain. Where's a group definition? How would you configure a filter for a group? Or a user for that matter. (Answer: you would require a much more complex data model, and functions to access and modify it).
I'll stick to OO for the top level paradigm I use until something better comes along.
OO being dead is fake news perpetuated by ignorant know-it-alls.
There is zero terminology related to implementation. NOT A single function was defined. All it is are function signatures analogous to your classes.
Read the pseudo code and understand it before you criticize there is enough information in the user tuple type to filter off of users and emails.
You think you are seeing over complexity but my code is actually more simpler and straightforward then your code. NOT to be insulting but you can't see it not because the code is complex but because you lack knowledge.
I'll tell you this. I completely understand your code and I completely understand my code. My judgement is less biased. Respond to me when your level of understanding reached mine.
Nothing gets my blood boiling more then deliberatr ignorance. Read and understand my code before you bitch.
The email class does work fine. It's just full of extra cruft. Can you not see how the code is 10x more complex when you use DI patterns?
To accomplish the task all you need is an implementation of an email function. That's it. This email function is replicated IDENTICALLY as a single method under the Email class, which indicates that all the classes, objects and dependency injecting that is surrounding the method are pointless abstractions over what already works.
If I give a programmer a task. His task is to create an api that can email someone a message with a subject and tell me whether the message is successfully sent. All I need is this:
I do not need this: The definition of the Email object vs the email function is about 10x more complex with little benefit. The send_mail method is virtually IDENTICAL to the email function, the objects and patterns you are building are just extra cruft you are layering around the email method. Not to mention you literally have to define 5 extra objects as well. It's obvious to me that most of the code is 100% cruft yet why does everyone who is knee deep in design patterns gravitate towards that Email object? I look at Email object and I think: I get what you're doing, but WHY.. what is the point? Are people really unable to see why this abstraction is unneeded?Some clown once told me dependency injection (The example object pattern above) helps with unit testing. I agree. Literally, the Email object has 5 extra objects that need to be unit tested so more unit tests!. More lines of code = more unit tests = a better program... In all seriousness, the core method is send_mail and that cannot be unit tested because it's IO. Everything you build around that method is extra BS that only needs to be tested because you made it exist.
Sometimes it baffles me how people just start using design patterns everywhere. I think what's going on is that people reach this cathartic state when they understand a new mind bending concept. Dependency Injection is a bit of mind bender, and this catharsis people feel when they reach a point of understanding blinds them from the downsides of patterns in general. Actually dependency injection is a stupidly simple concept, it's the word "dependency injection" that adds artificial complexity to the concept by making it sound cool and harder to understand.