This is the fifth day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

Design mode of play — Bridge mode

One. Introduction problem

In game engines, the same graphics can be drawn by different graphics engines, and the same graphics engine can also draw different graphics. From the perspective of inheritance, it can be divided into the following levels.

Through analysis, we found that there are great problems in this design: (1) scalability problem: if the new graphics class, then it is necessary to add the classes under each drawing engine, and the same is true for the new drawing engine, so that the number of classes will be geometrically increased in the later period. (2) The principle of single responsibility is violated: the graph drawn by a class XXXX has two reasons for the change of this class. Simply put, the use of inheritance, whether a new type or a new brand, will involve another dimension of change.

Ii. Simple combination improvement:

There are two graphics classes Sphere,Cude and a drawing engine class OpenGL. Now what we want to achieve is to use the drawing engine to draw these two graphics. The general approach is to hold a reference to the drawing engine in a graphics class and call its drawing methods.

The specific code is as follows:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour {
	void Start () {
        Sphere sphere = newSphere(); sphere.Draw(); }}public class Sphere
{
    public string name = "Sphere";

    public OpenGL openGL = new OpenGL();
   
    public void Draw(){ openGL.Draw(name); }}public class Cube
{
    public string name = "Cube";
    public OpenGL openGL = new OpenGL();
    public void Draw(){ openGL.Draw( name); }}public class OpenGL
{
    public void Draw(string name)
    {
        Debug.Log("Draw it using OpenGL."+ name); }}Copy the code

If the requirements change now and we add a new drawing engine, DriectX, and use the new addition to draw, we need to hold references to DriectX in all our graphics classes and add a new drawing method implemented by DriectX. The following

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour {
	void Start () {
        //Sphere sphere = new Sphere();
        //sphere.Draw();
        //Cube cude = new Cube();
        //cude.Draw();
        Sphere sphere = new Sphere();
        sphere.DriecDraw();
        Cube cude = newCube(); cude.DriecDraw(); }}public class Sphere
{
    public string name = "Sphere";

    public OpenGL openGL = new OpenGL();
    public DriectX driectL = new DriectX();

    public void Draw()
    {
        openGL.Draw(name);
    }
    public void DriecDraw(){ driectL.DriecDraw(name); }}public class Cube
{
    public string name = "Cube";
    public OpenGL openGL = new OpenGL();
    public DriectX driectL = new DriectX();
    public void Draw()
    {
        openGL.Draw( name);
    }
    public void DriecDraw(){ driectL.DriecDraw(name); }}public class OpenGL
{
    public void Draw(string name)
    {
        Debug.Log("Draw it using OpenGL."+ name); }}public class DriectX
{
    public void DriecDraw(string name)
    {
        Debug.Log("Draw with DriectX"+ name); }}Copy the code

Through the above analysis we can find that, if we continue to add graphics and graphics engine, and demand for graphics engine to change, so we want to change all the graphics class every time, it doesn’t accord with our open closed principle, if we carefully analyze their dependencies, also violated our dependency inversion principle can be found.

Iii. Final solution: Bridge mode

(1) Definition: Separate the abstract part from its implementation part so that they can change independently.

(2) Applicable environment: a. A class has two dimensions that change independently, and both dimensions need to be extended. B. If a system needs to add more flexibility between the abstract and embodied roles of components, avoiding the static inheritance relationship between the two levels, the bridge pattern can enable them to establish an association relationship at the abstraction level. C. For scenarios where inheritance is not desirable or where the number of system classes increases dramatically due to multi-level inheritance.

(3) UML diagram: 1)Abstraction (IShape shape) : Defines an abstract interface with an object reference of type Implementor

2) RefinedAbstraction (concrete Cube,Shpere, etc.) : Extends the abstract interface definition in Abstraction

3) Implementor (IRenderEngine base class) : an abstract Implementor

4) ConcreteImplementor (OpenGL) : Implementor interface.

(4) Concrete implementation: We can define the drawing engine abstraction base class IRenderEngine. We can define the abstraction method Draw in IRenderEngine, and then implement the respective drawing method Draw in subclasses, and define the common base class IShape for graphics. Let it hold a reference to the drawing engine abstraction base class IRenderEngine, passing in the concrete drawing engine classes at construction time. In this way, it is possible to change the drawing engine flexibly without modifying the various graphics classes.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        //Sphere sphere = new Sphere();
        //sphere.Draw();
        //Cube cude = new Cube();
        //cude.Draw();

        DriectX render= new DriectX();
        Sphere sphere = new Sphere(render);
        sphere.Draw();
        Cube cude = newCube(render); cude.Draw(); }}public class IShape
{
    public string name;
    public IRenderEngine renderEngine;
    public IShape(IRenderEngine renderEngine)
    {
        this.renderEngine = renderEngine;
    }
    public void Draw(){ renderEngine.Draw(name); }}public class Sphere:IShape
{
   public Sphere(IRenderEngine re) :base(re)
    {
        name = "Sphere"; }}public class Cube:IShape
{
    public Cube(IRenderEngine re) : base(re)
    {
        name = "Cube"; }}public abstract class IRenderEngine
 {
    public abstract void Draw(string name);

 }

public class OpenGL:IRenderEngine
{
    public override void Draw(string name)
    {
        Debug.Log("Draw it using OpenGL."+ name); }}public class DriectX:IRenderEngine
{
    public override void Draw(string name)
    {
        Debug.Log("Draw with DriectX"+ name); }}Copy the code

Now when we add a new drawing engine, SuperRender, and change the current drawing engine to it, all we need to do is add a drawing subclass that inherits IRenderEngine, implement the Draw method, and start by changing the DriectX class to SuperRender. I can easily implement the transformation.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        //Sphere sphere = new Sphere();
        //sphere.Draw();
        //Cube cude = new Cube();
        //cude.Draw();

        SuperRender render = new SuperRender();
        Sphere sphere = new Sphere(render);
        sphere.Draw();
        Cube cude = newCube(render); cude.Draw(); }}public class IShape
{
    public string name;
    public IRenderEngine renderEngine;
    public IShape(IRenderEngine renderEngine)
    {
        this.renderEngine = renderEngine;
    }
    public void Draw(){ renderEngine.Draw(name); }}public class Sphere:IShape
{
   public Sphere(IRenderEngine re) :base(re)
    {
        name = "Sphere"; }}public class Cube:IShape
{
    public Cube(IRenderEngine re) : base(re)
    {
        name = "Cube"; }}public abstract class IRenderEngine
 {
    public abstract void Draw(string name);

 }

public class OpenGL:IRenderEngine
{
    public override void Draw(string name)
    {
        Debug.Log("Draw it using OpenGL."+ name); }}public class DriectX:IRenderEngine
{
    public override void Draw(string name)
    {
        Debug.Log("Draw with DriectX"+ name); }}public class SuperRender : IRenderEngine
{
    public override void Draw(string name)
    {
        Debug.Log("Draw with SuperRender"+ name); }}Copy the code

Summary: 1. The bridge pattern decouples the strong relationship between abstraction and implementation by using the combinatorial relationship between objects, so that abstraction and implementation can vary along their respective dimensions.

2. Handle multiple inheritance structure, processing dimensional change of scene, we can inherit structure of dimensions will be designed to be independent, to make each dimension can be independent in the abstraction layer for expansion, this is the design of the bridge model thought, but the application of bridge model in “two dimensions” very strong change, sometimes even with changes in two dimensions, However, the dimension of change in one direction is not dramatic, in other words, the two changes do not lead to criss-crossing, and the bridge pattern is not necessary.

Iv. Other Application examples:

Characters and Weapons:

Characters: warrior, mage, assassin, gunslinger

Weapons: Long knife, short knife, dagger, tai Dao, pistol….