简体   繁体   中英

Generic class constraint where <T> is a type constraining the generic class

Perhaps not the most accurate title, but it's a little difficult to describe; perhaps you guys can help me out here? I'm writing a game using the MVC format, and I want each base class (controller, model, and view) to have a reference to their accompanying features, forming a sort of triangle (ie. A model has a reference to a controller that defines it, and a view that references it, etc. ) Much of these classes look like this:

public class Model {
  public Controller controller;
  public View view;

  public void Connect (Controller controller, View view) {
    this.controller = controller;
    this.view = view; 
  }
}

This is okay, but whenever I intend to pull up a ChildModel's controller, I'll need to cast to the appropriate ChildController to obtain the correct version. I could make a utility method/getter to fetch an appropriately cast item, but I'd rather not rewrite this piece of code for each and every child class. I thought I could solve this issue by making the base classes generic, but now I'm running into an issue where the newly generic classes need references to the class that's trying to define them, hence:

public class Model<V, C> where V : View<?, C> where C : Controller<?, V> {
  public Controller<?, V> controller;
  public View<?, C> view;

  public void Connect (Controller<?, V> controller, View<?, C> view) {
    this.controller = controller;
    this.view = view; 
  }
}

As you can see, this quickly gets messy in the base class. I don't know what symbol to place for (in reference to the example above) the Model that's attempting to define the constraints. Placing 'Model' into the question marks doesn't seem to compile either, as I run into a hellish boxing conversion issue.

Is there a way to accomplish what I'm after, or am I just trying to be too clever here? If this could work, I'd love to be able to declare child classes with the type constrained to their 'triangle', thus I could avoid needless casting or helper methods:

public class ChildModel<ChildView, ChildController> {

  public ChildModel () {
     this.controller <- calls ChildController type, not base type!
  }
}

Anyone have any ideas?

It looks like you are confusing ownership with interactions. Ownership implies that one owns the other, while interactions imply how they communicate with one another. MVC primarily defines interactions between the three participants, though you could say that a view and controller both own a model.

在此输入图像描述

In your code as shown, a class owns a property, therefore a controller class owns a view and a view owns a controller.

var model = new Model();
var view  = new View<Controller<Model, View<Controller, Model>, ...

This doesn't work with generics in the way you would like because the interactions become circular. It is the chicken and the egg problem: chickens come from eggs which are laid by chickens. We can solve most of the problem by giving the controller ownership of the view, and both the controller and view ownership of a model.

public class Model
{   
}

public interface IView<M>
{
    M Model { get; }
}

public class MyView : IView<Model>
{
    public MyView(Model model)
    {
        Model = model;
    }

    public Model Model
    {
        get;
    }
}

public interface IController<V, M>
{
    M Model { get; }
    V View { get; }
}

public class MyController : IController<MyView, Model>
{
    public MyController(MyView view, Model model)
    {
        View = view;
        Model = model;
    }

    public Model Model
    {
        get;
    }

    public MyView View
    {
        get;
    }
}

We still used generics to do this, and you have easy access to most of the information so far without introducing circular references.

class Program
{
    public static void Main()
    {
        var model      = new Model();
        var view       = new MyView(model);
        var controller = new MyController(view, model);
    }
}

Now if you want to make sure the view has a reference to the controller, you can do this via a property.

view.Controller = controller;

You could disregard everything I just showed you - and go the property injection route. This means instead of taking in the dependencies by the constructor, which creates circular reference restrictions on how the objects can be created, you can simply do this.

var model = new Model();
var view  = new View();
var controller = new Controller();

model.View = view;
model.Controller = controller;

view.Controller = controller;
view.Model = model;

controller.View = view;
controller.Model = model;

Whatever method you use, the trick is to avoid the circular dependency issue that you have in your current code. Most MVC frameworks provide rich data binding which breaks the direct coupling between the classes, but if you don't have that, you have to either write something or find something, or work within the confinements of the language rules.

There are a lot of ways to solve this. As I wrote this there was another answer posted so you should also look at that.

Here's my suggestion. 1. You should use the Controller as the main part of your MVC pattern. The controller should get the information from the Mode, process it and then call the view.

Here's my base class for the Controller

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Inheritance.Classes
{
    public class Controller<T, U> where T : Model, new() where U : View, new()
    {
        protected T _model;
        protected U _view;

        public Controller()
        {
            this._model = new T();
            this._view = new U();
        }

        public Controller(T model, U view)
        {
            this._model = model;
            this._view = view;
        }

        public string ParentFunction()
        {
            return "I'm the parent";
        }
    }
}

Note, I have also a Model and View base class. Since they are empty for the moment, I won't show you the code

Then, I can define my child classes. For example, I will make a PageController, PageModel and PageView. They will all inherite from their BaseClass.

Note : Once again, PageModel and PageView are empty. They are only used for the inheritance

PageController.cs

using Inheritance.Page;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Inheritance.Classes
{
    public class PageController : Controller<PageModel, PageView>
    {
        public PageController():base()
        {

        }

        public PageModel Model
        {
            get
            {
                return base._model;
            }
        }
    }
}

So as you can see, you will specify the Model class and the View class only inside the PageController.

To use your classes, you can do as follow :

        PageController controller = new PageController();

        //We can access the parent function
        Console.WriteLine(controller.ParentFunction());

        //Function defined into the controller.
        PageModel model =  controller.Model;

I think this is what you want:

public class GameModel : Model
{
    public int ID { get; set; }
}

public class GameView : View<GameModel, GameView>
{
    public float FOV { get; set; }
}

public class GameController : GameView.BaseControler
{
    // Set ID
    public GameController()
    {
        Model.ID=100;
        View.FOV=45f;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var gm = new GameModel();
        var view = new GameView();
        var ctrl = new GameController();

        view.Connect(gm, ctrl);

        Debug.WriteLine(view.Model.ID);
    }
}

public class Model
{

}

public class View<TModel,TView> where TModel : Model where TView : View<TModel, TView>
{
    public TModel Model { get; private set; }
    public BaseControler Controler { get; private set; }

    public void Connect(TModel model, BaseControler controler)
    {
        this.Model=model;
        this.Controler=controler;
        this.Controler.Connect(model, this as TView);
    }
    public class BaseControler
    {
        public TView View { get; private set; }
        public TModel Model { get; private set; }

        public void Connect(TModel model, TView view)
        {
            this.Model=model;
            this.View=view;
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM