11 ms·
Classes vs. Data Structures
- hudon 7y agoThe claim that "an object is a set of functions that operate on implied data elements" has a strange corollary because of how in modern OO languages like Java or C#, there is no syntax or popular naming convention to tell the difference between a data structure and an Object. For example, in Java, a LinkedList object is actually not a linked list, it is a set of functions that operate on an implied linked list. If the system needed direct access to the data for whatever reason, we'd need to explicitly have a LinkedList data structure object that only contained the data (the values and their pointers), as well as a second class, the LinkedListOperator, that contains all the functions (add, first, etc.). Likewise, in the author's examples, there'd be a Square class and a SquareOperator class. I was going to say that Haskell addresses this by putting values in data types and behaviors in "type classes", but then I remembered that functions can be values... which is now making me think that the reality is probably more abstract or complex than the author here is letting on.
- danmaz74 7y agoIn a purely oop language, data structures exist as an implementation detail, but you can never access them directly (as in, bypassing the object interface). That's by design.
- hudon 7y agoI get that in your application, you may want to keep a linked list behind its interface 90% of the time. However, considering your system as a whole, at some point you may want to take that linked list data and write it to a database, in which case the cleanest thing is to bypass the interface and extract the "data structure object" so to speak and deal with it in a database-related object, rather than encumbering your LinkedList object with database behaviors.
- pault 7y agoI have these kinds of arguments with my C# co-workers all the time, and they usually have some annoyingly reasonable solution like "create a database serialization class with a linked list consumer". :)
- wvenable 7y ago> in which case the cleanest thing is to bypass the interface and extract the "data structure object" That doesn't seem like the cleanest to me. Instead, you'd use the interface to read/write to the list as you write/read it to storage. At no time do you want to reach into the LinkedList object and access it's internal mechanism.
- jayd16 7y agoThis is a violation of OOP. Instead, consider methods that produce and consume a serialized representation of the data instead. Things like Java serialization and Python pickle attempt to do what you say and are considered failures (or at least security risks) because they allow a third party to act on object implementation internals. Security aside, a denormalized representation of data could be different than the implementation specific representation that's encapsulated inside an object.
- Silhouette 7y agoBut this is partly a self-made problem, because in this OOP model you have decided that the internal representation of your data is to be hidden and therefore the data is only accessible via the provided interface. In practice, it is debatable how often that is helpful when you're implementing generic data structures. An alternative is to specify the representation explicitly and provide a set of functions designed to work with it, but also to allow direct access by other functions when that is useful. You can still build a layer of more abstract interfaces on top and write more generic algorithms in terms of those interfaces rather than any specific concrete representation, as for example Haskell's typeclass system does.
- jayd16 7y ago
- stcredzero 7y agoIn a purely oop language, data structures exist as an implementation detail, but you can never access them directly In Smalltalk, you can have a TreeNode class and instantiate TreeNode instances. This is often the way that binary trees are implemented in Smalltalk. In that case, data structures exist as a design of interacting objects. In that case, you can molest them fairly directly using the object interface. The same goes for Java and C#. Basically, you can design in such a way, that you can essentially have C or Fortran in any language, even a pure OO language like Smalltalk. The right way to do this in Smalltalk, is to use a Facade, which can be used to hide the guts of your data structure.
- antisemiotic 7y agoIn Haskell this can be pushed further, by making existential types, i.e. opaque type that's only known to implement some type class. This doesn't seem to be that useful in Haskell (I think it's because of the immutability), but trait objects in Rust are pretty much the same and are used more often. As a side note, I hope that the situation with pre- and postcondition checking improves (runtime verficiation like in Racket seems too heavyweight for me, dependent types alone are kind of clunky, type refinements are either bolted onto existing languages (LiquidHaskell, F7) and don't always fit, or exist in very obscure ones (F*)), and we can drop information hiding for the sake of safety alone. I don't like how today the "safe" option is to severly limit what you can do with a data type (want to safely index an array? just use an iterator! oh, you wanted to index two arrays at once? just zip them and hope the compiler makes the tuples go away! you wanted O(1) indexing? too bad, no can do).
- lmm 7y ago> This doesn't seem to be that useful in Haskell (I think it's because of the immutability) I suspect it's because of the polymorphism. Why write a function that operates on some unknown a for which a Foo implementation exists when it's so easy to write a function that operates on any a for which a Foo implementation exists? The only time I've seen existentials used is for safety - in particular ST-style monads where a fancy type is used to ensure that you can't "leak" state out of the monadic context.
- marcosdumay 7y agoOptparse-applicative has a very nice use of existential types to break a definition loop.
- antisemiotic 7y agoIt indeed makes no sense for passing arguments, but it does for storing things. For example, both `[Int]` and `[Float]` can be passed to a function of type `Num a => [a] -> a`, but these are distinct types, and neither can store a mix of ints and floats. It ultimately boils down to the difference between static and dynamic polymorphism (Even more visible in Rust, where regular polymorphism works exactly like C++ templates, while trait objects pack a "v-table" together with a structure. In Haskell it's a little more blurry since "static" polymorphism is already implemented in a way that doesn't easily translate to templates, for example allowing polymorphic recursion).
- jayd16 7y ago*DTO is the closest to such a convention as its implied that data representation is the goal.
- tetrep 7y agoHaskell does address it exactly as you say. Functions and values both being expressions is much less complex. As an example, imagine if you couldn't freely substitute 2+2 and 4. Functional languages say those are the same, imperative languages say one is a value and one is a function call. It's not particularly intuitive if you're not used to high level math or functional programming, but it really is a lot simpler (not that it doesn't have downsides/leaks in the abstraction, but that's another discussion).
- millstone 7y agoI'm not sure that functions alone are sufficient. That's why Haskell supports existential types, and there isn't a convention for distinguishing them so the critique applies.
- jmkni 7y ago> ...because of how in modern OO languages like Java or C#, there is no syntax or popular naming convention to tell the difference between a data structure and an Object. In C#, you create a class when it's an object, and a struct when it's a data structure, or am I missing something? ie: // Data Structure public struct Foo { public string Bar { get; set; } } // Object public class Bar { public string Foo { get; set; } } You can check to see if something is a Data Structure like so: typeof(Foo).IsValueType (true) typeof(Bar).IsValueType (false)
- _pmf_ 7y ago> In C#, you create a class when it's an object, and a struct when it's a data structure, or am I missing something? Yes and no. Due to the technical limitation that (unlike in C++[0]) structs in C# cannot derive from other structs, DTOs in C# are often implemented (or rather: generated) as classes to allow derivation from a base class that has certain behavioral hooks (say, for misc. custom serializers). [0] See https://www.fluentcpp.com/2017/06/13/the-real-difference-between-struct-class/ https://www.fluentcpp.com/2017/06/13/the-real-difference-bet... (the real technical difference between struct and class in C++ is just that the default visibility modifier for a struct is public and the default visibility modifier for a class is private; both can use inheritance and have virtual methods)
- jmkni 7y agoCool thanks
- gugagore 7y agoThis conversation reminds me of https://en.wikipedia.org/wiki/Expression_problem https://en.wikipedia.org/wiki/Expression_problem . I don't understand: "but the existence of the data structure implies that some operations must exist." Grounding it out to a specific data structure, the existence of `List` implies that e.g. `sort` exists? That direction makes less sense than `sort` implies the existence of e.g. `List`(something to be sorted).
- narag 7y agothe existence of `List` implies that e.g. `sort` exists? The existence of List implies that operations must exist to insert an element in a list, access to elements in a list, find out the size of the list, etc. Edit: BTW 'implies' is the the magic word in the text. It's what creates all the appearance of meaning. Try to replace it what something else. Now I remember why I disliked Plato so much.
- gugagore 7y agoI thought about using `[]`or `indexOf` as examples of operations, but my question still remains: what is implicit about it? It's part of the public interface of `List`. Not at all like the private members of an object, which I think was the analogy being made.
- deleted 7y ago[deleted]
- lalaithion 7y agoThose aren't implicit because the "public interface" is the Object List, not the Data Structure List. struct list { float node; struct list *next; } Above is the data structure; it implies operations. Below is an interface (class, in the article); it implies data. #define LIST_H float index(struct list *ls, int i); int find(struct list *ls, float x); void sort(struct list *ls);
- breischl 7y ago
- mistrial9 7y agovery interesting and worthwhile! Data has gravity; and data dependancies are more costly than code dependancies, are two lines that are current. Objects may possibly have broader uses that what is described here ("business data applications") but within the definition given, the description of Object and operations on object make a lot of sense. This post is worth re-reading a few times.
- justinpombrio 7y agoThe first set of points: > Classes make functions visible while keeping data implied. Data structures make data visible while keeping functions implied. > Classes make it easy to add types but hard to add functions. Data structures make it easy to add functions but hard to add types. is known as the Expression Problem https://en.wikipedia.org/wiki/Expression_problem https://en.wikipedia.org/wiki/Expression_problem. The last point: > Data Structures expose callers to recompilation and redeployment. Classes isolate callers from recompilation and redeployment. is only somewhat true. I suspect it would be more accurate to say that it's a matter of indirection: static dispatch isolates callers from recompilation; static dispatch exposes callers to recompilation; calling a function pointer isolates callers from recompilation; calling a function directly exposes callers to recompilation. (All of this in statically typed languages.) Though this isn't my area of expertise. Perhaps someone else knows more? [Edit: sounds like these "expose" cases often don't cause recompilation either.]
- charlieflowers 7y agoRegarding the claim ... > Data Structures expose callers to recompilation and redeployment. Classes isolate callers from recompilation and redeployment. ... most projects (maybe all?) I've worked on in 20+ years deployed a full set of new bits upon release, rather than trying to differentiate at the level of which source code files were and were not touched. So this strikes me as a carryover from many years ago when working on large C++ projects with slow compile times was even more painful than it is today. Any counterpoints?
- lliamander 7y agoWell, it also affects things like packaging and swapping out alternative implementations of different components.
- ridiculous_fish 7y agoConsider any scenario in which you don't control all of the source code that gets run. For example, software which supports 3rd party plugins, Apple releasing a new version of iOS, etc.
- sdegutis 7y agoThis is really hard to follow and I'm not sure I'm understanding it the way he intended. Correct me if I'm wrong, but he seems to be saying that, to avoid breaking consumers of your library often by changing the implementation, hide the implementation details behind classes, right? This seems to be a common reaction to someone who experiments with the "freedom of functional programming" where that freedom means operating on and returning raw data structures that OOP usually hides behind private variables. That's still bad practice, even in code that heavily uses FP, and good code usually mixes FP and OOP properly, so that you're given functions when you're meant to have functions, and data when you're meant to have data. This is how I've been writing JavaScript for a few years now, and it's not how I've seen Java or Clojure usually written.
- Twisol 7y agoI don’t think he’s exactly advising any particular action. Rather, data structures and objects tend to be badly conflated, and there’s a lot of value in clarifying the distinction between them. You’ll use each in different circumstances, for different reasons, by weighing the needs of the system against the design tools at your disposal. In Rust, we keep the same distinction by modeling data structures as structs and enums, and modeling the “object” side by traits (whether static- or dynamic-dispatch). Traits decouple a consumer from the particular data and emphasize a behavioral contract, allowing any data structure to implement the desired behavior.
- sdegutis 7y agoSo basically mixins, right? Those were hard to use correctly in Ruby, because you might have multiple whose behavior clash because they can access the same data and were not written with each other in mind. I wonder how Rust solves that.
- Twisol 7y agoNot quite. A mixin is a piece of code written once and transcluded into another module. Traits are more related to OOP interfaces: every type implements one in its own way. The difference with interfaces is that traits can be implemented separately from the definition of the underlying data type, which clarifies the distinction between inherent operations on a specific data structure, and derived operations that bind it to a more general contract of use.
- h8liu 7y agoWhat the author calls "objects" (or "classes") is really often just "interfaces". > An Object is a set of functions that operate upon implied data elements If this is replaced with: > An Interface is a set of functions, often operate upon implied some implied data elements (but not necessary). Everything in the article will probably be less confusing.
- F_J_H 7y agoAnd while the discussion takes place and the debate rages between classes vs. data structures, there's some poor analyst or data scientist who just needs access to the damned data to load it into a pandas data frame to do things that those designing the objects and data structures never dreamed of in the first place...
- fpoling 7y agoThe dependency discussion is wrong. Changing code of a function does not lead to recompilation of callers in most static languages. So if one change circlePerimeter, only that has to be recompiled. But if one changes data structure, then the callers has to recompiled. But this is also true for objects. In C++ changing data typically leads to recompilation of both implicit and explicit data structures. Essentially objects and data structures behaves the same.
- dllthomas 7y ago> Changing code of a function does not lead to recompilation of callers in most static languages. I don't know how we're quantifying so as to assess "most", but at least in some popular static languages there are circumstances (most notably inline functions) where callers are likely to be recompiled and circumstances (dynamic loading) where they clearly won't be. > But if one changes data structure, then the callers has to recompiled. Only if you're changing parts of the data structure that are visible to callers. For instance, if your API operates on opaque handles you can change the underlying data structure however you'd like without recompiling the caller.
- mcguire 7y ago"No, ORMs extract the data that our business objects operate upon. That data is contained in a data structure loaded by the ORM." Technically, ORMs are a set of waldos that you operate inside a glove box in order to manipulate the data in the DB without getting DB cooties on you.
- RcouF1uZ4gsC 7y ago>OK, OK. I get it. The functions that operate on the data structure are not specified by the data structure but the existence of the data structure implies that some operations must exist. This reminds me of Linus's quote: " I'd also like to point out that unlike every single horror I've ever witnessed when looking closer at SCM products, git actually has a simple design, with stable and reasonably well-documented data structures. In fact, I'm a huge proponent of designing your code around the data, rather than the other way around, and I think it's one of the reasons git has been fairly successful (). () I will, in fact, claim that the difference between a bad programmer and a good one is whether he considers his code or his data structures more important. Bad programmers worry about the code. Good programmers worry about data structures and their relationships. " https://lwn.net/Articles/193245/ https://lwn.net/Articles/193245/
- wvenable 7y agoI think the author makes a good argument about how data structures and objects/classes are different. I'm one of those people who designs the data first and then the code. But when you design the code, you shouldn't be making it a one-to-one mapping with the data. When you design your classes, they should be the best representation for the programmer to use and not necessarily just identical to storage format. As well, the most convenient structure for the user of your classes is most likely not the best format for storage.
- dmux 7y agoI may need to reread it, but wasn't one of the key arguments in Parnas' "On the Criteria To Be Used in Decomposing Systems into Modules" that by modeling around data we fall into the trap of writing code that's "temporally" dependent?
- bcp2384 7y agoNot every language is class-based...
- skybrian 7y agoThinking about client-server architecture (for example, a database) can clarify things. Encapsulation means you never have the canonical data. The server is the system of record. You can get data back in response to queries, perhaps even a full data dump, but it's a snapshot. You can also send commands to mutate data on the server. Typical applications aren't allowed to do a complete replacement (restoring from backup). On the other hand, data is better thought of as what's going over the network. Messages consist of data. Encapsulation is almost meaningless; if you want to keep something private from the receiver, don't include it in the message in the first place (or use encryption, maybe). Anyone reading the data has to be able to understand the format, or at least, ignore what they don't understand. In the degenerate case where the caller and implementation live within the same process, the same types often get used both for message transfer (in function arguments and return values) and storage. There is widespread "cheating" for performance reasons, and it can get confusing. For a transient process, it might not make sense to think in these terms at all. (Traditional Smalltalk used persistent images and client-server style encapsulation makes somewhat more sense there.) You can also "cheat" by using the same schema for data transfer and storage, or having a trivial mapping between them. This can introduce unnecessary coupling, but there are systems where it works. (Consider that you can make a full clone of a git repo and it doesn't encapsulate any data.)
- stupidcar 7y agoWhenever I hear "Socratic dialog", I reach for my revolver. Is there any other form of teaching so irritating and patronising? You might have a brilliant store of insight to impart, but if you insist on trying to do so via a twee, affected and unbelievable conversation with a Mary Sue wise professor, I'm going to write you off as insufferable before the fawning moron you have as proxy for your audience utters their first "Oh, wow! So you mean that straw-man you just put in my mouth isn't true?"
- ergothus 7y agoI completely understand and almost always share your reaction. Socratic CAN be done well - The novel Starship Troopers is actually like that...the arguments may not be your first pick, but you consider them all reasonable, and then you discover you're supporting fascism! It requires you to back up and find where you made a false connection. Unfortunately, the most common usage is at best ineffective, for as you say, they will dictate some logical jump I don't agree with, so when they disprove it in favor of their point I remain unconvinced as my own argument hasn't been addressed at all. At worst, it's infuriating.
- mamon 7y agoSocratic method is supposed to be done in person, between professor and students, with professor choosing next question based on student's answers that typically reveal gaps in their knowledge. Doing it in form of made-up conversation in a book is a travesty.
- zcid 7y agoHave you read Starship Troopers? It's one of Heinlein's finest novels.
- thom 7y agoThe only thing more annoying is when people ape Why's (Poignant) Guide to Ruby and you have to follow the adventures of some tedious otter as it meets the rabbit people who ultimately explain pointers in a way which takes a thousand too many words.
- Jach 7y agoI guess this applies for Java and C++ style "classes". This does not precisely apply to the first ANSI-standardized OOP system, Common Lisp's. Standard classes do not own methods, instead methods are specializations of a generic function that stands alone and dispatches on the class types (or EQL values) of all its arguments. I'd really like it if Uncle Bob eventually has his fill of Clojure and moves on to explore what Common Lisp built decades earlier, then blogs about that too.
- hardwaresofton 7y agoTo add another point to the Common lisp over Clojure argument, DECLARE[0] offers a way to take advantage of type declarations natively. I stopped using Clojure and don't consider it for new projects because I think types are invaluable documentation now, and it pains me that clojure and it's community doesn't believe the same way (typed clojure[1] does exist but it's contentious). [0]: http://clhs.lisp.se/Body/s_declar.htm http://clhs.lisp.se/Body/s_declar.htm [1]: https://github.com/clojure/core.typed https://github.com/clojure/core.typed
- mrbrowning 7y agoThere are substantial differences between Clojure's constellation of protocols/records/multimethods and CLOS, but at least the feature of CLOS that you cite is exactly what multimethods in Clojure do, see: https://clojure.org/reference/multimethods https://clojure.org/reference/multimethods
- dreamcompiler 7y agoCame here to say exactly this and you already did, so thanks. It's amazingly liberating to use a language where generic functions are first-class, and classes don't own any methods. Once you've written code this way, the other way seems backward and restrictive.
- StefanKarpinski 7y agoSpot on. Multiple dispatch avoids the whole issue because methods are external and don't live inside of classes. Lisps, of course support multimethods, which is great. There are some down sides, though. They are opt-in (defmethod) and tend to have a significant performance hit associated with them. Someone needs to anticipate your need to add types and/or functions and think it's worse sacrificing performance for that ability. Julia, builds on this tradition but allows you to have your cake and eat it too. It has multimethods/generic functions and they are the only option—all user defined functions are multimethods. They also have excellent performance (they're used for everything, they have to). Of course, there's no free lunch and you do give up traditional separate compilation, but the degree composability it gives to the ecosystem is hard to comprehend without experiencing it. Simple, reusable data types are shared across the ecosystem with anyone adding whatever (external) methods they want. Generic code that handles a literally exponential explosion of argument types "just work"—and the compiler generates fast code. All without doing anything special, since multiple dispatch is the default and only way functions work.
- bendbro 7y agoThis doesn't make sense to me: "Right. Now consider the area function. Its going to have a switch statement in it, isn’t it?" Perhaps I am nitpicking, or perhaps I am reading this wrong, but I would not design the square data structure to have a perimeter function. The square data structure should just expose the data that describes a square (length, width). Adding higher abstractions (perimeter, etc) on top of the data structure only serves to create the trumped up problem later described in the dialog. The perimeter method should be defined in the Square class, where perhaps a "StraightLinesOnlyPolygonMixin" could define the perimeter method. In general, I cannot see why a Data Structure would define computational methods. You are tightly coupling logic to the underlying data source, which is wrong when that logic obviously could apply to any underlying data source (I don't care if my square is backed by RDB, S3, a hardcoded instance, etc) The perimeter method, and probably the Square class, should be the same.
- ivan_gammel 7y agoYou are getting it wrong. Data structures do not own functions, instead they are passed to functions. So you have shapes and somewhere else you have perimeter function which has switch statement to determine the algorithm of calculation based on the type of the structure.
- bendbro 7y agoAh, and what owns these functions? And more pressingly, why would you ever pass a data structure to a function that had more than one algorithm to compute a result? The data structure (or perhaps some intermediary (an adapter?)) should own the algorithm within a function that computes only on that data structure. This ensures that all methods associated with your data structure are obviously and explicitly associated (in a single file, class, whatever). The alternative, as outlined in the dialog, is to spread a bunch of switches all around your code. Given these two possibilities, why would one choose to place switches in disparate places throughout your code?
- ivan_gammel 7y agoDid you ever write a program on C? In procedural languages functions are either global or, sometimes, encapsulated in namespaces or modules. Data structures come from that world and do not encapsulate any behavior. In object-oriented languages like Java data structures simply do not exist and are usually emulated via anemic models.
- solinent 7y agoA class is fundamentally about implementing object semantics. This simply means everything (eg. all objects) are an instance of some class. It has methods which can be used to operate or communicate with other objects or itself. Data-structures are put most simply as ways of organizing data. The organization of the data implies a specific layout--a way of representing your data as a table of integers, ie. in RAM. After reading the article, I don't see a meaningful distinction between objects and data structures. A data structure can be represented as an object, especially in OOP languages where it must be. A general class doesn't necessarily lay out its data in any particular way--which allows abstraction over the data representation of the class. However, some classes are made which are designed in a manner which guarantees a certain data layout. std::vector with its contiguous memory requirement comes to mind. To add to this, the c++ conception of a "concept" or haskell's concept of a "typeclass", or even a generic class, is really what this article is talking about. Or even Java or Go's interfaces. There is absolutely no way to guarantee a specific data structure through an interface, typeclass, or concept, since they fundementally do not mention their data representation at all.
- 725686 7y agoMaybe a little tangential but it immediately came to my mind Alan Perli's quote: "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures." I think I first heard this from Rich Hickey and made so much sense.
- micimize 7y agoSeems to me that with the definitions given, structures and objects aren't opposites, they're corollaries. A database table is both a data structure and a collection of functions for accessing/manipulating it (SQL). An ORM maps between the "Object Oriented" objects and the "Relational" objects that are tables. Interesting the author thinks about the the api exposed by a table as the "data structure" itself rather than an object. Pragmatically, we tend to refer to the "objects" at the lower level of abstraction as data structures. Is [...] a function that defines an array, or the array itself?
- mannykannot 7y agoWhy does every software example always involve shapes? Because they allow us to avoid discussing the complications that arise when entities have lifetimes, over which, at different stages, different operations are meaningful.
- b0rsuk 7y agoBecause it's one of the very few cases where class inheritance is an elegant solution.
- gowld 7y agoWhen Uncle Bob discovers subclasses, friends, decorators, and mixins, his mind will be blown.
- tydok 7y agoClasses vs Data Structures, or maybe Objects vs Data Structures, or maybe Classes vs Objects, or maybe Data vs Data Structures, or maybe Data vs State, or maybe Classes vs Types, or maybe OOP vs FP, or maybe I don't know what I'm talking about...
- DeathArrow 7y agoPeople are starting to use data oriented design instead of OOP. Data oriented design doesn't hide state, is generally faster and easier to comprehend as it doesn't abstract too much. https://www.youtube.com/watch?v=QM1iUe6IofM https://www.youtube.com/watch?v=QM1iUe6IofM https://www.youtube.com/watch?v=yy8jQgmhbAU https://www.youtube.com/watch?v=yy8jQgmhbAU https://www.youtube.com/watch?v=rX0ItVEVjHc https://www.youtube.com/watch?v=rX0ItVEVjHc
- danmaz74 7y agoData oriented design makes sense in video games where perfomance Is very important, but in most business applications having good abstractions which are flexible and easily maintainable is much more important than optimising for cache usage.
- atoav 7y agoI tend to disagree — although I thought the same one or two years ago. Data oriented design doesn’t automatically mean you have to sacrifice useful abstractions on the altar of performance. You’ll have to find different abstractions and ways of composing them together to get a maintainable, flexible result. In fact I find good data driven designs easier to maintain than good OOP ones..
- eska 7y agoI keep hearing this from OOP proponents, but I just don't find it to be true in my experience. Programs written with DOD in mind have very clear data flow and only pass on and use data that is relevant. Programs written with OOP in mind primarily care about some notion of beautiful code and abstractions, which I find to be highly subjective. As a result they generally have very muddy data flow where e.g. unrelated data is passed around that isn't even required to implement a feature. This creates all kinds of poor modularization, dependency hell, huge monoliths, difficult testing (mocking, fakes, etc...), among many other problems. Whenever I have had to rewrite large parts of a program, I have always found it to be easier to do this in a DOD program rather than an OOP program. The biggest reason why DOD is used in video game programming to begin with is flexibility in mixing and matching functionality of game objects (entity-component-systems etc).
- steve-chavez 7y ago> Since the database schema is a compromise of all the various applications, that schema will not conform to the object model of any particular application. Here it jumps to objects instead of database VIEWs, that can be tailored for each application. There's no need for complex object models when you embrace the db and you don't treat it as a dumb store.
- atoav 7y agoInteresting read. What immediately sprung to my mind was Rust's Trait system which sort of manages to give you the best of both worlds. With Traits you can implement common behaviour/functions for multiple datastructures. When I started using Rust I wasn’t used at all to seperate data and behaviour that strictly, but it makes sense. OOP paradigms were still hardwired in my head so the hardest part was actually wanting to do it in that decoupled way. Something about a car object that has wheel objects and a car.drive() function gives you a good feeling as a programmer, but sometimes it is more effective to stay with the data structure and describe the car as a struct of vectors which implement a Driveable trait..
- nfrankel 7y agoAm I the only one who has trouble with the dialog form?
- roelschroeven 7y agoNo. The way the text is written obfuscates the point the text is trying to make, IMO.
- xvector 7y agoI personally found the dialogue form amazing and made the article very understandable.
- hotBacteria 7y agoI like the shapes problem because I actually encountered it and it made me think. I'm not sure about the switch approach described in the post: function area(shape) switch shape.type case "square": return shape.side ** 2 case "circle": return 2 * PI * shape.radius case "triangle": return ... case "segment": return 0 case "polygon": return ... ... case "oval": return ... You can have a lot of cases, some of them requiring non trivial code... Eventually you write a function for each case and it's more work than adding a method for each shape because you still need to write the switch... Classes seem work better than structures here. But then you want to handle intersections The switch approach doesn't seem realistic: function intersection(shapeA, shapeB) if(shapeA.type == "circle" AND shapeB.type == "circle")... if(shapeA.type == "circle" AND shapeB.type == "square")... if(shapeA.type == "square" AND shapeB.type == "circle")... ...//uh oh you have nShapes**2 cases to handle But java classes or not better: where do you define Circle-Square intersection? In Circle? In Square? Even with multiple dispatch the solution is not ideal. You now have some things related to Circle (area, perimeter...) in the Circle.blub file, and intersection(Circle, Circle) wich only works with Circles is now in intersections.blub... I don't see a good solution and sometimes I feel like the problem is more with our tools (code in text files) rather than programming paradigms
- eska 7y agoI have to admit I don't quite understand your issue. To me it seems like you've used a lot of OOP and cannot befriend the idea that the data structure (e.g. "Circle" in file GeometryTypes.blub) and operations that are performed with it (e.g. collisions in "CollisionDetection.blub") are completely separate. There should be no discussion whether the circle type and collision belong in the same file, while combinations are in some other file. Think of it like this: if you're going to add 3d rendering of circles, will you put that in Circle.blub together with collision detection? Wouldn't you rather add it to 3DRenderer.blub? That said, ultimately it doesn't really matter. If you're going to implement collision detection like this, then yes, you will have a combinatorial explosion. This is not a language issue. Switching from Java to some other language with a different form of dispatch will not save you from implementing a lot of algorithms when adding bezier curves into the mix. The practical approach is reduce the problem to a common case, e.g. to turn the collision shapes into a set of triangles first, and then perform triangle-triangle collision detection.
- Aromasin 7y agoI enjoy the authors "question/answer" style of writing. I often find myself asking questions just like this when reading an article, and find that when the author isn't "question focused" they never get answered. It seems that when the entire writing style pivots on the idea, the author forces themselves to consider more Q's to pad out the content and, incidentally or otherwise, provide more A's.
- hoseja 7y agoI find it smug and insufferable, like someone on tumblr lecturing you about demisexual marxist theory or something.
- flakiness 7y agoWas surprised how Uncle Bob getting better at trolling these days. He's been provoking, but somewhere in recent years he turned himself to a troll. (Or he's just talking to his audiences, who aren't HN readers.)