12 ms·
I get what he's trying to say. I think he expresses it very unclearly. I'll try to elucidate the main idea in an example: If I give a programmer a task. His ta
by crimsonalucard 9y ago
I get what he's trying to say. I think he expresses it very unclearly. I'll try to elucidate the main idea in an example:
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:
func email(address: str, subject: str, message: str) -> bool
I do not need this:
Class Email(AddressObject, SubjectObject, MessageObject, ConfigObject, ClientObject).send_mail()
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.
- nathanaldensr 9y agoOf all the patterns you could pick on, you pick on one of the most useful in modern OOP; however, I think you actually intend on picking on dependency inversion, not injection. Dependency injection is simply an automated way to satisfy constructor dependencies in OOP languages where your intent is to invert a dependency graph. I won't go into why dependency inversion (see: Dependency Inversion Principle, the "D" in SOLID) is so valuable because much has already been written on the topic. 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.
- crimsonalucard 9y ago>Of all the patterns you could pick on, you pick on one of the most useful in modern OOP; however, I think you actually intend on picking on dependency inversion, not injection. Dependency injection is simply an automated way to satisfy constructor dependencies in OOP languages where your intent is to invert a dependency graph. I won't go into why dependency inversion (see: Dependency Inversion Principle, the "D" in SOLID) is so valuable because much has already been written on the topic. 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.
- majidazimi 9y agoExcept that one could unexpectedly/unintentionally pass "#@%^%#$@#$@#.com" to email function, but Email class works perfectly fine.
- crimsonalucard 9y agoSee my response to the other guy above. You want to validate an email? make a validation function. You don't need to put that validation function under a class then inject it into an object then send the mail. 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.
- rickbad68 9y agoThis trivial example does not illustrate the usefulness of OO design. Extending the example imagine the requirement is that the target of an email may be either a single user or a group of users, and in addition, email filters can be configured for both individual users and groups of users 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?
- crimsonalucard 9y agoYour example has a globally defined function sendEmail. Big OOP languages such as java or C# do not permit that style. You must define all functions as a method of an object if you want to follow the OOP style. You are illustrating a hybrid style of procedural and OO. 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.