26 ms·
Eric Lippert's "Wizards and Warriors" blog posts seem appropriate here, especially as an example of how tracking state and OO can clash pretty badly. A common
by hprotagonist 9y ago
Eric Lippert's "Wizards and Warriors" blog posts seem appropriate here, especially as an example of how tracking state and OO can clash pretty badly.
A common problem I see in object-oriented design is:
A wizard is a kind of player.
A warrior is a kind of player.
A staff is a kind of weapon.
A sword is a kind of weapon.
A player has a weapon.
But before we get into the details, I just want to point out that I am not really talking about anything specific to the fantasy RPG genre here. Everything in this series applies equally well to Papers and Paychecks, but wizards and warriors are more fun to write about, so there you go.
OK, great, we have five bullet points so let’s write some classes without thinking about it! What could possibly go wrong?
https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/ https://ericlippert.com/2015/04/27/wizards-and-warriors-part...
https://ericlippert.com/2015/04/30/wizards-and-warriors-part-two/ https://ericlippert.com/2015/04/30/wizards-and-warriors-part...
https://ericlippert.com/2015/05/04/wizards-and-warriors-part-three/ https://ericlippert.com/2015/05/04/wizards-and-warriors-part...
https://ericlippert.com/2015/05/07/wizards-and-warriors-part-four/ https://ericlippert.com/2015/05/07/wizards-and-warriors-part...
https://ericlippert.com/2015/05/11/wizards-and-warriors-part-five/ https://ericlippert.com/2015/05/11/wizards-and-warriors-part...
- jacquesm 9y agoThat's mixing up properties, ownership and state. Those are very different concepts.
- bdjewkes 9y agoThat's effectively the conclusion that the series comes to in the end.
- mehrdadn 9y agoIt may be obvious to you but it's very hard to see this without a fair bit of experience.
- CPAhem 9y agoIndeed. Whether you use OO or state machines depends on the architecture of your problem. There is unlikely to be a one-size fits all solution to every class of problem. State machines are used extensively by HDL hardware design tools, and are easier to debug.
- bargl 9y agoI don't think an up-vote is enough. Thanks for this. I love his explanation of Liskov substitution principle. Eric Lippert is smart, I love his Stack Overflow answers.
- hprotagonist 9y agoThink it's worth submitting on its own?
- adzm 9y agoAbsolutely. Though would be better as a single article rather than a series.
- hprotagonist 9y agolooks like someone else got it.
- bargl 9y agoIt may be better as ShowHN rather than a single article. But it was a great read.
- munificent 9y agoI love this series. One little rule I've stumbled onto over the years that this series revolves around is: When I'm stuck on a software design problem, pick some random part of the program and see what happens if I make it first class. In this case, Eric takes the game rules and turns them into objects. (Essentially the Command pattern[1], which is close to my heart[2].) You can go overboard with this, of course, but I've found time and again if it seems like I can't get my code to hang together, it's usually because I'm missing a noun — a reification of some part of my problem that I can pass around and do stuff with. [1]: https://en.wikipedia.org/wiki/Command_pattern https://en.wikipedia.org/wiki/Command_pattern [2]: http://gameprogrammingpatterns.com/command.html http://gameprogrammingpatterns.com/command.html PS: Another solution to Eric's initial problem with warriors, wizards, swords, and staves is to be more precise about what capability Player has. If Warriors can only wield Swords and Wizards can only wield Staves, then it's not the case that Player's Weapon field can be set with any weapon. So one option is to make Weapon an abstract getter in Player. Then add setters and fields in Wizard and Warrior for the specific types. If all you have is a Player, you can see what their wielding, but not change it. Then, to wield something, you need to know what kind of player you're dealing with first. Of course, that doesn't scale very well to lots and lots of business rules as in later in the series. But it works if you have a relatively small number of constraints in your subclasses — you just push them up such that the superclass API only exposes the intersection of all of the subclasses' allowed operations.
- mephitix 9y agoBuilding a game is a probably the best way to learn and apply state machines since a lot of it is all about how to manage state effectively since there are so many pieces of state and interactions between them. Also, since Bob is too humble to say it - http://gameprogrammingpatterns.com http://gameprogrammingpatterns.com is an amazing architecture resource for programmers in all disciplines, not just gamedevs. He's got another book called http://craftinginterpreters.com/ http://craftinginterpreters.com/ that I've been eager to check out.
- turboladen 9y agoCrafting interpreters is great!
- mehrdadn 9y agoHoly cow, these blog posts are everything I've been trying to say about OOP but never been able to put into succinct examples and explain clearly... and then some more. Thanks so much for sharing this (and to him for writing). Now I can finally point people somewhere instead of just getting a blank stare back when I tell them OOP is hard.
- nemothekid 9y agoI read once that the biggest issue with OOP is that everyone gets taught the “Dog inherits Animal” style which is completely worthless.
- GiorgioG 9y agoOne of the biggest issues with OOP is that it’s hard ;) I gave up attempting to get good at it after trying to read Bertrand Meyers’ Object-Oriented Software Construction book. If I need to be able to keep 1300 pages worth of info in my head to write well designed software, maybe it’s the wrong approach.
- Terr_ 9y agoIf I had my druthers, I'd put the teaching-ratio of interfaces versus inheritance at like 95%/5%. Treat concrete inheritance primarily as a useful work-saving shortcut. Basically, I'm one of those people who might unironically write `class DefaultDog implements Dog extends AbstractAnimal`.
- quickthrower2 9y agoYes because you don't have to inheritance "all the things". I am suspicious any time I see inheritance in my C# code base. It's rarely needed and causes a lot of pain when used extensively. I'm looking at you Credit Note and Invoice!
- tzahola 9y agoInheritance considered harmful. Go got this right.
- mcguire 9y agoInheritance is a way of relating two or more pieces of code; it's a programming abstraction. If you try to apply it to the problem domain, by "modeling" things, for example, you're in for a bad time.
- seanmcdirmid 9y agoThe antithesis is when inheritance should be used but isn't. Inexperienced programmers tend to ideological extremes in either camp.
- platz 9y agoyes, using composition instead of inheritance, as well as a "Structure of Arrays" (SoA) instead of an "Array of Structures" (AoS), to form a loose collection of arrays one can iterate over very quickly, and add properties to at run time, to program games is a mature game architecture described by http://en.wikipedia.org/wiki/Entity_component_system http://en.wikipedia.org/wiki/Entity_component_system which has been popular in the last decade. Oddly Eric never mentions ECS or SoA in the series, even though it solves the problem. (He only suggests in part 5 to use a Rule Engine, instead of inheritance)
- stcredzero 9y agoSo what if you did away with "is-a" and used "has-a" instead? Add "Weapons Skill" objects, including those which represent incompetence.
- Nition 9y agoThis is great. I've come up with just about every one of his "what if we..." examples on my own trying to find the same sorts of solutions over the years. Does anyone know a good code example (preferably C# but anything similar would do) of a rules system like what he describes in the final part?
- hprotagonist 9y agohttps://github.com/NRules/NRules https://github.com/NRules/NRules NRules is possibly what you're after, and maybe http://www.antlr.org/ http://www.antlr.org/ to parse rules.
- Nition 9y agoThanks!
- metaobject 9y agoI realize Lippert holds more knowledge about OOP and programming in his spleen than I have in my brain, but he mentions in the first post that the first attempt violates C# rules. But, if working in C++, the interface could specify either (Weapon & weapon) or (Weapon * weapon) could it not?
- keithnz 9y agoI think a lot of bad things happened when the advice around how to determine classes started by looking at the nouns in the problem domain. OO is fundamentally about messaging not abstract data types. So if you start your design from trying to determine ADTs instead of trying to work out the kinds of message conversations that need to go on you end up with mess of types. In the wizards and warriors we see a large amount of modelling of a domain with rules. But we, even at the end, still have little idea of what messaging is going to be going on, there is a hint about attacking werewolves. But we've already made a lot of assumptions about what our first class entites are. Often what can happen is we find, while we thought we modelled it ok and even sorted out a rule based system, it still seems difficult for our objects to have conversations. maybe what would be better is some thing that can attack other things and wizards, weapons, and warriors are all simply modelled by some composable set of stat modifiers.
- aryehof 9y agoThe original advice was to use nouns to "identify candidate classes", rather than "determine classes". Sadly when confronted with a problem, the majority of programmers can get no further than writing down lots of nouns, perhaps dozens of them, with no idea how to combine the few actually required into an object-oriented design. OO isn't fundamentally about messaging. That is incorrectly assuming that object-orientation began with Smalltalk, Alan Kay and Dan Ingalls. Smalltalk's "messaging with objects, all the way down" approach largely ended with its popular use and the widespread adoption of Java. Java's object model is entirely based on Simula (James Gosling Sept 2017).
- e12e 9y agoThe battle is lost now, but I think it's unfortunate that we use "OO" both for class oriented domain modelling, and object oriented state persistence with message passing for composition. Technically the latter is about object instances, the former about object/class (data/domain) modelling.
- mnm1 9y agoGreat series. He really gets to the major problem with OOP. It's not at all obvious what should be modeled or how and changing things once they're wrong is close to impossible. No wonder so few beginners pick it up and do it well. In the last part, what he's describing reminds me of clojure. A language where both data and functions are first class citizens and where functions operating on data is the default paradigm. That's the huge difference I see: in a language oriented around data like clojure, the architecture he describes is almost obvious from the start to experienced programmers and it can be slowly built even if one starts on the wrong path. In OOP languages, there are so many wrong paths and the right path is almost never clear or obvious. Even when the right path is clear, getting there from the current state of the code might be impossible without starting over (think how one would get from an architecture based on his first examples to one based around rules without a rewrite). While the author defends his use of OOP, it's clear imo he's still using the wrong tool for the job. Why use an OOP language that makes data a second class citizen hidden in objects rather than a first class citizen that the language is designed around? As they say, show me your data structures and I will know your architecture. Show me your code/functions and I know nothing.
- cutler 9y agoSecond that for Clojure. OOP is a mental straightjacket. When I started back-end programming I was faced with a fork in the road signposted on one side "Java" and on the other "Perl". I spent a long time with Dietel and Dietel's Java tome but somehow OOP just felt wrong. In Perl you just seemed to get on with it. Data and functions. Yes, you could bless a hash to get your OOP if you really wanted to but it was only later that the community became obsessed with OOP and Moose. So, I travelled down the Perl road and consequently found functional programming, and Clojure in particular, to be natural and easy to learn. Listening to other programmers who followed the traditional OOP path, I get the impression that they will defend OOP even when it's glaring deficiencies are staring them in the face. What is it about OOP? I don't get it. Get rid of it and give yourself a chance to approach problems differently. Bottom-up programming, especially with Clojure and its REPL, make programming a real joy. OOP is Stockholm syndrome masochism.
- 9y ago
- bushin 9y agoShame that Swift is pretty much confined to iOS app development. protocol Weapon {} class Staff: Weapon {} class Sword: Weapon {} protocol Player { associatedtype T: Weapon var weapon: T { get set } } class Wizard: Player { var weapon = Staff() } class Warrior: Player { var weapon = Sword() }
- schoen 9y agoIf you look through the later items in the series, it seems to get more complex than this. In particular, a character can use either 0 or 1 weapons at a particular moment, and either class can also use the Dagger weapon. It's not that each character inherently only has, and uses, the only weapon appropriate to that character -- this would make things quite a bit simpler.
- bushin 9y agoWell, maybe someday Dotty will be a mainstream language... trait Weapon class Sword extends Weapon class Staff extends Weapon class Dagger extends Weapon trait Player { type T <: Weapon } class Wizard extends Player { type T = Dagger | Staff var weapon: T = null } class Warrior extends Player { type T = Dagger | Sword var weapon: T = new Sword }
- icebraining 9y agoclass Wizard extends Player { type T = Dagger | Staff var weapon: T = null } The billion-dollar mistake in a new language? What a waste.
- boubiyeah 9y agoYeah that freaking blows. They keep it for compatibility with java. It's not idiomatic code though, you would use an Option.
- icebraining 9y ago
- badsectoracula 9y agoThe first four articles are interesting and i was following along, but i am not buying the last solution - it is way too complicated and over-engineered. It is telling that unlike the other four articles, the last one has no implementation code. What i would do is simply have Item (base for Weapon, Potion, etc) have a "IsUsableBy(Player p)" which returns true in the default implementation (which would be the case for the majority of items) and the objects that have special considerations check the player class themselves. Similarly an Animal (or Entity or Organism or whatever) class would have a "OnDamage(Animal source, int damage)" that by default calls -say- "ApplyDamage(int damage)", but special animals - like the werewolves - may want to filter the result so that the damage is doubled when they are on holy ground or when the class of source is Paladin. Also the IsUsableBy and OnDamage functions could check against Item objects in an inventory (e.g. Item could also provide a "FilterDamage" and "VetoUsageOf" so that an HolyCross can multiply all damage from Undead enemies by 0.25 but veto using any UnholyItem based class - truth be told here, you probably need a tag system for some of that stuff instead of relying on classes alone) and on an active Effect list (would also be able to do the same sort of vetoing and filtering and the Animal class could also provide a HasEffect method for use by the other classes to make decisions about - e.g. a PaperDoll might refuse to talk to you if you have the OnFireEffect :-P). To me that sort of setup feels more natural and intuitive than the user-command-state-rule stuff mentioned in the article. You can't really generalize it in a way that applies to other things (e.g. a MagicSpell would need its own set of functions and such) but i believe that there is a thing as too much generalization. (note that the above are when talking about the common Java/C++/C#/etc like OOP languages, in something like C i'd go with a more data and/or script driven approach...)
- a_t48 9y agoThis is all about composition over inheritance. What if I want a sword that has a flask built in? How do I reconcile the fact that Potion and Weapon are two different branches of the inheritance tree? His points about pushing things out to a data file are also very valid. Granted, the author may have taken it a little far, but the sentiment is right.
- badsectoracula 9y ago
- reificator 9y agoI prefer the related example of enchanted weaponry. * A Sword is a type of weapon. * A Fire Sword is a type of Sword. * A Mace is a type of weapon. * A Mace of Ice is a type of Mace. * A Sword of Ice and Fire is a type of Sword.
- xapata 9y agoWhat about if you hold the sword by the blade and hit someone with the pommel, is it a kind of Mace then?
- gordo4 9y agojust use inverted sword asset on mace weapon if you're lazy. melee is what you're talking about though. probably best to make melee a different weapon configuration and change to it when wielding. or make melee a weapon modifier. but the assets would differ, at least, in how the attack is drawn, but we could be talking about only a backend here, and UI is irrelevant.
- xapata 9y agoSo there's some weird Sword method that changes the sword to a Mace? Seems bug-prone. What if there's a special quest that triggers only if you're carrying a Mace? The message boards will be full of clever people saying, "Guess what, if you switch sword grips, you can actually get the Mace quest!"
- rapala 9y agoOne way to get at the end conclusion that the original modeling is wrong is simple logic. If given 1. Player can have a Weapon 2. Sword is a Weapon 3. Wizard is a Player The from the statement "Wizard cannot have a Sword" follows a contradiction that "Player cannot have a Sword".
- larksimian 9y agoI like and agree with his posts, but the last one does feel like a bit of a bait and switch: The first post talks about the difficulty of getting compile time errors for Wizard wielding a Sword The last post completely abandons the notion of statically checking anything about the Weapon assigned to a Player(beyond the most general case of is Player and is Weapon). I don't actually have enough experience with Erlang/Elixir to know but I thought their function overloading (http://raganwald.com/2014/06/23/multiple-dispatch.html http://raganwald.com/2014/06/23/multiple-dispatch.html) still provides multiple dispatch with compile time checks? The proposed Rules solution is more elegant to use(for the programmer, if not the person defining the Rules) but it does nothing(or at least very little) to help you check that the business logic is correctly encoded. Yes, you're likely going to avoid your program crashing, but that's just because you've given up on encoding the business logic entirely. At least where compile time checking is concerned. Maybe I'm expecting too much of the type system and that's the actual lesson of the blog posts.