UnityRef is currently in early development. Some features may be incomplete and/or not functioning.

UNITYREF

Your Pit Stop For Solving ANYTHING in Unity

architecture

[OOP] Halt Broken Inheritance: Secure Code with Liskov Principle

Solution

project architecturesoftware designbest practices

Unity 2021.x - Unity 6.3.x

Published Sat, Mar 21

Issue

 A common design challenge in RPG development involves the misuse of inheritance, leading to complex, unmanageable code hierarchies. Developers often struggle with identifying appropriate use cases for subclassing, resulting in LSP violations where derived types break the expected behavior of their base classes, making the codebase brittle and difficult to debug.

Inheritance is most effective for subtype polymorphism when the LSP is strictly maintained, ensuring derived types remain interchangeable with base types without logic errors.

Explanation

Apply inheritance primarily for subtype polymorphism where a derived type is a subtype of another, and both exhibit different behaviors for the same action. Adhere to the LSP: objects of a base class must be replaceable with objects of their derived classes without altering program correctness.

Avoid over-reliance on real-world “is a” relationships that lead to LSP violations. For instance, if your derived class inherits from your base class, ensure that operations like SetHeight or SetWidth on your derived class do not unexpectedly change the behavior expected from your base class. Violating LSP causes unpredictable behavior and debugging difficulties.

For interfaces, segregate large interfaces into smaller, focused contracts. This allows types like your script to implement only the behaviors they genuinely support, preventing empty method implementations that violate the interface contract and lead to critical bugs or deadlocks.

Additional Tips

  • Prioritize Composition over Inheritance to minimize tight coupling between deep class hierarchies.
  • Use virtual and override keywords intentionally to maintain the contract defined by the base class.
  • If a derived class must throw a NotImplementedException, it is often a sign of an LSP violation.

Copy


using UnityEngine;

public class ArchitectureExample : MonoBehaviour
{
    void Start()
    {
        // Demonstrating an LSP violation where a Square changes Rectangle logic
        Rectangle rect = new Square();
        CalculateDimensions(rect);
    }

    void CalculateDimensions(Rectangle r)
    {
        r.SetWidth(5);
        r.SetHeight(10);
        
        // If LSP is followed, area should be 50. In this violation, it results in 100.
        Debug.Log($"Expected Area: 50, Actual Area: {r.GetArea()}");
    }
}

public class Rectangle
{
    protected float width;
    protected float height;

    public virtual void SetWidth(float w) => width = w;
    public virtual void SetHeight(float h) => height = h;
    public virtual float GetArea() => width * height;
}

public class Square : Rectangle
{
    // Overriding both causes the other to change, violating the Rectangle contract
    public override void SetWidth(float w)
    {
        width = w;
        height = w;
    }

    public override void SetHeight(float h)
    {
        width = h;
        height = h;
    }
}

Related Posts Haven't quite found a solution to your problem? We think these posts might help you.

Content inspired by a Unity discussion post.