Delphi MVC和循环单元参考

时间:2015-04-28 10:02:30

标签: delphi

我正在慢慢地将一个leagacy应用程序转换为MVC风格(目前为新代码和大规模更改的代码)。目前,控制器是按需创建的:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var firstDigit: UITextField!
    @IBOutlet weak var secondDigit: UITextField!
    @IBOutlet weak var thirdDigit: UITextField!
    @IBOutlet weak var fourthDigit: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "goPrevious", name: "deletePressed", object: nil)

        firstDigit.secureTextEntry = true
        secondDigit.secureTextEntry = true
        thirdDigit.secureTextEntry = true
        fourthDigit.secureTextEntry = true

        firstDigit.keyboardType = .DecimalPad
        secondDigit.keyboardType = .DecimalPad
        thirdDigit.keyboardType = .DecimalPad
        fourthDigit.keyboardType = .DecimalPad

        firstDigit.becomeFirstResponder()
        secondDigit.enabled = false
        thirdDigit.enabled = false
        fourthDigit.enabled = false
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func goPrevious() {
        if secondDigit.isFirstResponder() {
            secondDigit.enabled = false
            firstDigit.enabled = true
            firstDigit.becomeFirstResponder()
        } else if thirdDigit.isFirstResponder() {
            thirdDigit.enabled = false
            secondDigit.enabled = true
            secondDigit.becomeFirstResponder()
        } else if fourthDigit.isFirstResponder() {
            fourthDigit.enabled = false
            thirdDigit.enabled = true
            thirdDigit.becomeFirstResponder()
        }
    }
    // You need to connect each text field to an IBAction (using sent events editing changed) – 
    @IBAction func firstChanged(sender: UITextField) {
        if let digitOne = sender.text.toInt() {
            println(digitOne)
            sender.enabled = false
            secondDigit.enabled = true
            secondDigit.becomeFirstResponder()
        } else {
            sender.text = ""
        }
    }

    @IBAction func secondChanged(sender: UITextField) {
        if let digitTwo = sender.text.toInt() {
            println(digitTwo)
            sender.enabled = false
            thirdDigit.enabled = true

            thirdDigit.becomeFirstResponder()
        } else {
            sender.text = ""
        }
    }

    @IBAction func thirdChanged(sender: UITextField) {
        if let digitThree = sender.text.toInt() {
            println(digitThree)
            sender.enabled = false
            fourthDigit.enabled = true
            fourthDigit.becomeFirstResponder()
        } else {
            sender.text = ""
        }
    }

    @IBAction func fourthChanged(sender: UITextField) {
        if let digitFour = sender.text.toInt() {
            println(digitFour)
            sender.enabled = false
        } else {
            sender.text = ""
        }
    }
}

随着越来越多的代码被转换,我想创建一个提供控制器的主类(FooController := TFooController.Create(GuiProvider, Model); FooController.EditFoo(); //show Edit-Dialog for Foo ):

TMainController

我的问题是,我需要访问一个控制器内的其他控制器。 (例如,我需要访问FooController内部的BarController)。

知道如何干净利落地解决这个问题吗?或许我的基本想法有缺陷?

我考虑的可能解决方案:

解决方案1(不工作):

我的第一个想法是为每个控制器提供对TMainController的引用:

FooController := MainController.GetFooController();
FooController.EditFoo();

但由于循环单位参考,这不会起作用。因为TMainController需要知道每个其他控制器,并且每个其他控制器都需要对TMainController的引用。

解决方案2(工作但丑陋):

解决方案1的解决方法是为TMainController创建一个基类。然后我可以将这个基类传递给其他控制器,并需要转换为TMainController来访问其他控制器:

//Edit-Dialog for Foo contains a button to edit Bar that belongs to Foo
procedure FooController.PerformActionEditBarOfFoo();
begin
    //do something
    BarController := FMainController.GetBarController();
    BarController.EditBar()
    //do some other things
end;

这很有效,但这似乎更像是黑客。

解决方案3(几乎不可能)

另一种解决方案是在创建FooController时提供所有需要的控制器。但是,如果BarController需要其他控制器等,那么这可能是巨大的。

解决方案4(对我来说不可能)

另一个解决方案可能是使用像Spring这样的依赖注入框架。但我不能在现有的代码中促进这种根本性的改变。

2 个答案:

答案 0 :(得分:2)

避免循环引用的解决方案之一是使用接口。有几种方法可以做到这一点,有些方法更简单,有些更复杂,具体取决于您现有的代码。

<强> 1。解决方案:定义IMainController接口并通过全局变量

使用它

将您需要在其他控制器中访问的MainContoller功能定义为完全独立的单元中的接口,并通过全局MainController变量使用它。这不是最佳解决方案,但它可能是在现有代码库中实现的最简单的解决方案。它还允许稍后以最小的重构实现第二个解决方案。

由于TMainControllerTInterfacedObject下降,它将被引用计数,因此您必须将其引用存储在接口引用中,以便引用计数正常工作。你不应该自己Free

unit MainControllerInterfaceUnit;

interface

uses
  FooControllerUnit,
  BarControllerUnit;    

type
  IMainController = interface
    function GetFooController: TFooController;
    function GetBarController: TBarController;
  end;

var
  MainController: IMainController;

implementation

uses
  MainControllerUnit;

initialization

  MainController := TMainController.Create;

end. 
unit MainControllerUnit;

interface 

uses
  FooControllerUnit,
  BarControllerUnit,
  MainControllerInterfaceUnit;

  TMainController = class(TInterfacedObject, IMainController)
  public
    function GetFooController: TFooController;
    function GetBarController: TBarController;
  end;

使用界面和全局变量,您可以跳过在MainControllerUnit中添加FooControllerUnit,并在您的使用条款中只有MainControllerInterfaceUnit

unit FooControllerUnit;

implementation

uses
  BarControllerUnit,
  MainControllerInterfaceUnit;

procedure TFooController.PerformActionEditBarOfFoo();
var
  BarController: TBarController;
begin
    BarController := MainController.GetBarController();
    BarController.EditBar()
end;

<强> 2。解决方案:定义IMainControllerIFooControllerIBarController接口,并将MainController作为参数传递

这是更好的解决方案,因为所有依赖项都被定义为接口。但是,它需要对现有代码进行更多更改。

unit ControllerInterfaceUnit;

interface

type
  IFooController = interface
  ... 
  end;

  IBarController = interface
     procedure EditBar; 
  end;

  IMainController = interface
    function GetFooController: IFooController;
    function GetBarController: IBarController;
  end;

implementation

end. 
unit MainControllerUnit;

uses
  ControllerInterfaceUnit;

  TMainController = class(TInterfacedObject, IMainController)
  public
    function GetFooController: IFooController;
    function GetBarController: IBarController;
  end;
unit FooControllerUnit;

uses
  ControllerInterfaceUnit;

  TFooController = class(TInterfacedObject, IFooController)
  protected
    MainController: IMainController;
  public
    constructor Create(const AMainController: IMainController);
  end;

procedure FooController.PerformActionEditBarOfFoo();
var
  BarController: IBarController;
begin
    BarController := MainController.GetBarController();
    BarController.EditBar()
end;

答案 1 :(得分:2)

以下是使用泛型的解决方案

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:gravity="center">

    <TextView
        android:id="@+id/tab_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="11dp"
        android:paddingBottom="11dp"
        android:textSize="14dp"
        android:textStyle="bold"
        android:maxLines="1" />

</LinearLayout>
unit MainController;

interface

uses
  System.Generics.Collections,
  System.SysUtils;

type
  TMainController = class
  private
    FDict: TDictionary<TClass, TObject>;
  public
    constructor Create;
    destructor Destroy; override;

    function GetController<T: class>( ): T;
    procedure RegisterController<T: class>( AController: T );
  end;

implementation

{ TMainController }

constructor TMainController.Create;
begin
  inherited;
  FDict := TObjectDictionary<TClass, TObject>.Create( [ doOwnsValues ] );
end;

destructor TMainController.Destroy;
begin
  FDict.Free;
  inherited;
end;

function TMainController.GetController<T>: T;
begin
  Result := FDict[ T ] as T;
end;

procedure TMainController.RegisterController<T>( AController: T );
begin
  FDict.AddOrSetValue( T, AController );
end;

end.
unit FooController;

interface

uses
  MainController;

type
  TFooController = class
  private
    FMainController: TMainController;
  public
    constructor Create( AMainController: TMainController );
    procedure EditFoo;
  end;

implementation

uses
  BarController;

{ TFooController }

constructor TFooController.Create( AMainController: TMainController );
begin
  inherited Create;
  FMainController := AMainController;
end;

procedure TFooController.EditFoo;
begin
  FMainController.GetController<TBarController>.EditBar;
end;

end.
unit BarController;

interface

type
  TBarController = class
  public
    procedure EditBar;
  end;

implementation

{ TBarController }

procedure TBarController.EditBar;
begin
  WriteLn( Self.ClassName + '.EditBar called' );
end;

end.