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