4

We are coding a small game and have a Player class. This Player class has certain properties which you would persist in a database like Id, Level, Health.

We would like a Player to be able to kill another Player. For this we need a method which reduces the Health of the Player.

To accomplish this do we:

  • Create a method on the Player entity called DealDamage(Player to)?
  • Create a separate class which we do not persist in the database called WorldObject with a method DealDamage(WorldObject to) which is inherited by the Player class? The list of methods might become very large at a point. In which application layer do we put this class?
  • Create a separate class which we do not persist in the database called DamageController with a method DealDamage(Player from, Player to) and split up the logic across different controllers? This will probably become confusing?
  • ...

The thought of not having methods on persisted entities seems attractive to me since it sounds like you are splitting up the business logic from the data layer logic. But can and should it be done?

Christophe
  • 74,672
  • 10
  • 115
  • 187
Vqf5mG96cSTT
  • 167
  • 3
  • How would you persist methods of an object to begin with? Methods aren't particular to any object, they exist as part of its class, so it makes no sense to store a separate copy for each instance. Also, they don't fit into the columns-and-rows model that databases typically offer you. – Kilian Foth Aug 04 '18 at 08:36
  • 1
    @KilianFoth That's the point exactly: you cannot persist methods nor should you. Do methods then still belong in your entities which you do persist? Does it not pollute your data layer with logic? – Vqf5mG96cSTT Aug 04 '18 at 09:01
  • No, it doesn't. Storing objects as database records is conceptionally problematic for many reasons (this is called the *object/relational impedance mismatch*), and the treatment of methods is only one of these, and not even the foremost. That doesn't mean that using databases as a persistence method isn't often the best solution. – Kilian Foth Aug 04 '18 at 11:25
  • If it's about persistence of the method itself, this brings us back to early research of OO-databases and the language/performance barrier. They never reached a production grade to my knowledge (yes you can store LISP classes and methods in a database, but how to read them back in type-safe languages?). If it's about whether or not to have domain methods on a persistent class, it's the question of whether or not use the [active record pattern](https://www.martinfowler.com/eaaCatalog/activeRecord.html): there are pros and cons but no universal rule. – Christophe Aug 04 '18 at 13:56
  • @bdebaere what kind of persistence do you need: is it when you restart the game to reconstruct the last game state ? Or is it some "real-time" persistence, where the database is shared between several application servers and is used to communicate game state changes between the different servers ? – Christophe Aug 05 '18 at 10:23

2 Answers2

1

While there are some schools of thought about Object orientation which insist in combining data entities with logic to align classes with physical entities, this is by no means a guideline that you should follow unless you truly believe it's the best solution to your particular problem (That approach may naturally lend itself to some problems, but frequently causes more problems than it solves if just followed blindly). There are other schools of thought which emphasise clean separation of concerns, and prioritise 'SOLID Principles' instead.

Physical entities as classes is a valid outlook on class design, but is also extremely constraining and only provides a narrow perspective which doesn't resolve issues about how classes might interact with each other, how to test them, or what happens when behavioural logic could involve multiple physical entities. Nor does it necessarily consider the way that software naturally changes and evolves over time as its requirements change or issues are discovered in the original implementation.

It's important to keep in mind the goals of object orientation rather than treating it as a set of rules to be followed. Broadly, the goals are about grouping logically-related behaviour (methods) into classes, keeping clean separation between different unrelated classes/behaviour. The extent to which you decide to do that is up to you, but you can often make a lot of decisions based on whether something 'feels' helpful in the way you structure your program.

If something doesn't feel helpful, or it seems counter-productive, or leads to code which feels 'messy' and unnecessarily complex, then that should be enough reason not to do it. Instead consider the practical implications such as whether it makes your code easier to unit-test or whether the rationale for your code structure would be immediately obvious to other developers who are trying to reason over your code. Consider principles such as KISS, DRY and YAGNI.

It seems extremely unlikely that any core business/application logic would ever need to care about the specifics of CRUD operations relating to a data store, so it would seem rather odd to have the DealDamage method in the same class as a Save or Read method; plain common-sense dictates that these things should probably be unrelated.

For example, your DealDamage method probably isn't going to behave differently if the data happens to be stored in a SQL database compared with being stored in a Flat file or NoSQL store, so from a logical/semantic point of view it makes sense to keep them separated. Furthermore, if your DealDamage method does behave differently depending on those things, it might be an indicator of a possible problem in your design, or a problem with the way your requirements are specified.

There's absolutely nothing wrong in Object-oriented programming with creating plain 'entity' models whose purpose is merely to hold data which has been retrieved from a database. Nor is there anything wrong with creating stateless behavioural classes which have no data of their own and operate entirely on those entity models, in fact such a design is fairly typical in a Layered Architecture, and even necessary if you happen to be building a system based on a Microservices Architecture.

Naturally, it depends upon your requirements as to whether you need or would benefit from one of those architectural styles. In general, any kind of very small or trivial app may not get much benefit from the clean separation of concerns that you'd get from a more complex design, and you can get code working fairly quickly with the Just Do It approach which tends to ignore any wider concerns about design/architecture or possible future change.

More complex designs are intended to solve a multitude of problems that people typically associate with larger systems whose requirements are more complex and ever changing. Particularly where the complexity of those systems would make them difficult to reliably test or change as a monolith. The separation between layers/modules/subsystems allows those different components to be developed in isolation, more easily reused by other systems, more flexible/scaleable to meet future requirements, and easier for other developers to reason about.

Ben Cottrell
  • 11,656
  • 4
  • 30
  • 41
  • You're right, you don't have to follow object-orientation, however I disagree with everything else you've said. Object-orientation does not allow for pure data classes to exist, nor does it endorse 'stateless' objects. Layered architectures, as practiced today in the 'enterprise' field are **not** Object-oriented in any sense of the word. It's like saying it's ok to have mutable objects and non-referentially-transparent functions in functional-programming. No it's not ok. You may decide not to use these things (which *is* ok!), but then you have to call it something else! – Robert Bräutigam Aug 05 '18 at 10:13
  • @RobertBräutigam That is only one interpretation of 'object-orientation', but there are others, including that of Alan Kay who originally coined the term itself - his definition is not about objects at all, but about messaging between objects: https://softwareengineering.stackexchange.com/questions/46592/so-what-did-alan-kay-really-mean-by-the-term-object-oriented - the N-tier architectures and microservices would still be classified as 'object-orientated' under Kay's definition, and the use of stateless classes as well as pure data classes are also perfectly acceptable. – Ben Cottrell Aug 05 '18 at 12:40
  • Fair enough, there may be multiple interpretations. However, Alan Kay did write about the "object" being a thing on its own, an autonomous agent like a biological cell. He also referenced "Dataless programming" as being the goal, as in, getting away from having to specify the data to specify the functionality (i.e. data being hidden). I also doubt invoking setters and getters that communicate no logic whatsoever would qualify as "messages". – Robert Bräutigam Aug 05 '18 at 16:39
0

Lets coin some terms.

If you put your methods on the same object as your data then you are doing classic Object Orientated Programming (OOP). If you have no methods on your data objects and instead put them on Service classes, such as your DamageController, to which you pass the data objects then, this is often called Anaemic Domain Model (ADM).

Personally I find ADM an attractive approach for things like web services, worker processes or anything that's summed up as a business work flow.

If I have an Order object I might have a dozen different things you could do to it at different points in its lifecycle, but in any given program I am probably applying the same single process to a never ending stream of orders.

On the other hand, If I'm writing an Application where we have a number of persistent things such as a computer game with a dozen enemies on screen. I find OOP more attractive.

I will want to do several unpredictable things to the objects over their life cycle and in the code that does those things I might not have access to the service I would need. Its easier to put the Methods on the object. move the character, shoot its weapon, play the animation etc etc

However, I still want to avoid deep inheritance chains and this can be achieved by injecting the services into the Object and using composition ie.

public class Character
{
    private IPathFinder pathFinder;
    public void MoveTo(x,y)
    {
       var route = this.pathFinder.FindRouteTo(x,y);
       this.target = route.NextWaypoint();
    }
}

I'll still use a bit of Inheritance, such as Player : Character, Enemy : Character, but I have an option to prevent needing to go back too far.

Ewan
  • 70,664
  • 5
  • 76
  • 161
  • Does this mean you no longer divide your architecture into the three layers we see so often in business applications? Because at the moment it seems this would pollute your data layer with so called (business) logic. – Vqf5mG96cSTT Aug 05 '18 at 04:42
  • No, I still have layers. I use the repository pattern a lot – Ewan Aug 05 '18 at 07:58