Main Content

定义接口超类

接口

类定义的属性和方法构成接口,该接口决定类用户如何与类的对象交互。创建一组相关类时,接口定义所有这些类的公共接口。接口的实际实现可能因类而异。

以一组设计用于表示各种图形类型的类为例。所有类都必须实现 Data 属性,以包含用于生成图形的数据。然而,对于不同类型的图形,数据的形式可能有很大不同。每个类可以通过不同方式实现 Data 属性。

对于方法,也存在同样的差异。所有类都可以有一个创建图形的 draw 方法,但是,该方法的实现会根据图形的类型而变化。

接口类的基本思想是指定每个子类必须实现的属性和方法,而不定义实际实现。这种方式使您能够对一组相关对象强制实施一致的接口。在将来添加更多的类时,接口始终保持不变。

接口类实现图

此示例为用于表示特化图形的类创建一个接口。接口是抽象类,它定义子类必须实现的属性和方法,但不指定如何实现这些组件。

这种方式强制实施一致的接口,同时提供必要的灵活性来以不同方式实现每个特化子类的内部工作。

在本示例中,包文件夹包含接口、派生的子类和工具函数:

+graphics/GraphInterface.m  % abstract interface class
+graphics/LineGraph.m       % concrete subclass

接口的属性和方法

graph 类指定以下属性,子类必须定义这些属性:

  • Primitive - 用于实现特化图形的图形对象的句柄。类用户不需要直接访问这些对象,因此该属性具有 protected SetAccessGetAccess

  • AxesHandle - 用于图形的坐标区的句柄。特化 graph 对象可以设置坐标区对象属性。该属性具有 protected SetAccessGetAccess

  • Data - GraphInterface 类的所有子类都必须存储数据。数据的类型各不相同,每个子类都定义存储机制。子类用户可以更改数据值,因此该属性具有公共访问权限。

GraphInterface 类命名子类必须实现的三个抽象方法。GraphInterface 类还在注释中建议每个子类构造函数必须接受所有类属性的绘图数据和属性名称/属性值对组。

  • 子类构造函数 - 接受数据和 P/V 对组并返回对象。

  • draw - 用于创建绘图基元,并根据子类实现的图形类型渲染数据的图形。

  • zoom - 通过更改坐标区 CameraViewAngle 属性来实现 zoom 方法。该接口推荐使用 camzoom 函数来实现子类之间的一致性。由 addButtons 静态方法创建的缩放按钮使用此方法作为回调。

  • updateGraph - set.Data 方法调用的方法,用于在 Data 属性发生变化时更新绘图数据。

接口决定类设计

GraphInterface 抽象类派生的类包实现以下行为:

  • 创建特化 GraphInterface 对象(子类对象)的实例,而不渲染绘图

  • 在创建特化 GraphInterface 对象时,指定任一或不指定任何对象属性

  • 更改任一对象属性都会自动更新当前显示的绘图

  • 允许每个特化 GraphInterface 对象实现它需要的任何附加属性,以便让类用户控制这些特性。

定义接口

GraphInterface 类是抽象类,用于定义子类使用的方法和属性。抽象类中的注释说明预期的实现:

classdef GraphInterface < handle
   % Abstract class for creating data graphs
   % Subclass constructor should accept
   % the data that is to be plotted and
   % property name/property value pairs
   properties (SetAccess = protected, GetAccess = protected)
      Primitive
      AxesHandle
   end
   properties
      Data
   end
   methods (Abstract)
      draw(obj)
      % Use a line, surface,
      % or patch graphics primitive
      zoom(obj,factor)
      % Change the CameraViewAngle
      % for 2D and 3D views
      % use camzoom for consistency
      updateGraph(obj)
      % Update the Data property and
      % update the drawing primitive
   end
   
   methods
      function set.Data(obj,newdata)
         obj.Data = newdata;
         updateGraph(obj)
      end
      function addButtons(gobj)
         hfig = get(gobj.AxesHandle,'Parent');
         uicontrol(hfig,'Style','pushbutton','String','Zoom Out',...
            'Callback',@(src,evnt)zoom(gobj,.5));
         uicontrol(hfig,'Style','pushbutton','String','Zoom In',...
            'Callback',@(src,evnt)zoom(gobj,2),...
            'Position',[100 20 60 20]);
      end
   end
end 

GraphInterface 类实现属性 set 方法 (set.Data) 以监控 Data 属性的更改。另一种方式是将 Data 属性定义为 Abstract,并使子类能够确定是否为此属性实现 set 访问方法。GraphInterface 类定义一个调用抽象方法(updateGraph,每个子类都必须实现该方法)的 set 访问方法。GraphInterface 接口为整个类包施加特定设计,而不会限制灵活性。

用于所有子类的方法

addButtons 方法为 zoom 方法添加普通按钮,每个子类都必须实现这些方法。通过使用方法而不是普通函数,可使 addButtons 能够访问受保护的类数据(坐标区句柄)。使用对象 zoom 方法作为普通按钮回调。

function addButtons(gobj)
   hfig = get(gobj.AxesHandle,'Parent');
   uicontrol(hfig,'Style','pushbutton',...
      'String','Zoom Out',...
      'Callback',@(src,evnt)zoom(gobj,.5));
   uicontrol(hfig,'Style','pushbutton',...
      'String','Zoom In',...
      'Callback',@(src,evnt)zoom(gobj,2),...
      'Position',[100 20 60 20]);
end

派生具体类 - LineGraph

此示例只定义一个用于表示简单线图的子类。它派生自 GraphInterface,但提供对抽象方法 drawzoomupdateGraph 及其自己的构造函数的实现。基类 GraphInterface 和子类都包含在包 (graphics) 中,您必须使用它来引用类名:

classdef LineGraph < graphics.GraphInterface

添加属性

LineGraph 类实现在 GraphInterface 类中定义的接口,并添加两个附加属性 - LineColorLineType。此类为每个属性定义初始值,因此在构造函数中指定属性值是可选的。您可以创建没有数据的 LineGraph 对象,但无法从该对象生成图形。

properties
   LineColor = [0 0 0];
   LineType = '-';
end

LineGraph 构造函数

构造函数接受 structxy 坐标数据,以及属性名称/属性值对组:

function gobj = LineGraph(data,varargin)
   if nargin > 0
      gobj.Data = data;
      if nargin > 2
         for k=1:2:length(varargin)
            gobj.(varargin{k}) = varargin{k+1};
         end
      end
   end
end

实现 draw 方法

LineGraphdraw 方法使用属性值创建一个 line 对象。LineGraph 类将 line 句柄存储为受保护的类数据。为了支持类构造函数不使用输入参数,draw 会在继续之前检查 Data 属性以确定它是否为空:

function gobj = draw(gobj)
   if isempty(gobj.Data)
      error('The LineGraph object contains no data')
   end
   h = line(gobj.Data.x,gobj.Data.y,...
      'Color',gobj.LineColor,...
      'LineStyle',gobj.LineType);
   gobj.Primitive = h;
   gobj.AxesHandle = get(h,'Parent');
end

实现 zoom 方法

LineGraphzoom 方法使用 GraphInterface 类的注释中推荐的 camzoom 函数。camzoom 提供方便的接口来支持缩放功能,并使用 addButtons 方法创建的普通按钮以正确进行操作。

定义属性 set 方法

通过属性 set 方法,可以方便地在构造函数中首次更改属性值时自动执行代码。(请参阅属性 set 方法。)每当属性值更改时,linegraph 类都会使用 set 方法更新 line 原始数据(这会导致绘图重绘)。使用属性 set 方法可以快速更新数据图,而无需调用 draw 方法。draw 方法通过重置所有值为当前属性值来更新绘图。

三个属性使用 set 方法:LineColorLineTypeDataLineColorLineType 是由 LineGraph 类添加的属性,并且是该类使用的 line 基元特有的属性。其他子类可以定义对其特化所独有的不同属性(例如,FaceColor)。

GraphInterface 类实现 Data 属性 set 方法。但是,GraphInterface 类要求每个子类定义名为 updateGraph 的方法,该方法处理所使用的特定绘图基元的绘图数据更新。

LineGraph 类

以下是 LineGraph 类定义。

classdef LineGraph < graphics.GraphInterface
   properties
      LineColor = [0 0 0]
      LineType = '-'
   end
   
   methods
      function gobj = LineGraph(data,varargin)
         if nargin > 0
            gobj.Data = data;
            if nargin > 1
               for k=1:2:length(varargin)
                  gobj.(varargin{k}) = varargin{k+1};
               end
            end
         end
      end
      
      function gobj = draw(gobj)
         if isempty(gobj.Data)
            error('The LineGraph object contains no data')
         end
         h = line(gobj.Data.x,gobj.Data.y,...
            'Color',gobj.LineColor,...
            'LineStyle',gobj.LineType);
         gobj.Primitive = h;
         gobj.AxesHandle = h.Parent;
      end
      
      function zoom(gobj,factor)
         camzoom(gobj.AxesHandle,factor)
      end
      
      function updateGraph(gobj)
         set(gobj.Primitive,...
            'XData',gobj.Data.x,...
            'YData',gobj.Data.y)
      end
      
      function set.LineColor(gobj,color)
         gobj.LineColor = color;
         set(gobj.Primitive,'Color',color)
      end
      
      function set.LineType(gobj,ls)
         gobj.LineType = ls;
         set(gobj.Primitive,'LineStyle',ls)
      end
   end
end

使用 LineGraph 类

LineGraph 类定义由 graph 基类指定的简单 API,并实现其特化类型的图形:

d.x = 1:10;
d.y = rand(10,1);
lg = graphics.LineGraph(d,'LineColor','b','LineType',':');
lg.draw;
lg.addButtons;

点击放大按钮会显示为该按钮提供回调的 zoom 方法。

Window showing randomly drawn line segments

更改属性会更新图形:

d.y = rand(10,1); 
lg.Data = d;
lg.LineColor = [0.9,0.1,0.6]; 

现在点击缩小并查看新结果:

Window showing randomly drawn line segments

相关主题