首页 > 代码库 > 结合具体项目谈谈对抽象类的理解

结合具体项目谈谈对抽象类的理解

  抽象类面向对象的语言中经常使用的类,网上也有各种各样的剖析,大致上都是做了以下方面的对比:1 抽象类和普通类的区别。2 抽象类和接口的区别。 3 抽象类中的虚方法和抽象方法的区别,首先去找到这些重大的区别,然后针对具体的项目来谈谈对这些概念的理解。

  1 抽象类和普通类

      A.都可以被继承 。

  B.抽象类不能被实例化,只是用来继承的,普通类可以实例化。

  C.抽象方法只有含方法声明而没有方法体且必须包含在抽象类里面。

  D.子类继承抽象类必须实现抽象类中的抽象方法除非子类也是抽象类。

  E.抽象类中可以包含抽象方法也可以包含实例方法。

     2 抽象类和接口 

  抽象类(abstract class)可以包含功能定义和实现,接口(interface)只能包含功能定义,抽象类是从一系列相关对象中抽象出来的概念, 因此反映的是事物的内部共性;接口是为了满足外部调用而定义的一个功能约定, 因此反映的是事物的外部特性。

  相同点: 
  A.都可以被继承。 

  B.都可以有方法的声明 。

  C.都不可以被实例化 。

  D.子类必须实现基类的方法除非子类是抽象类。

  不同点: 
  A.抽象类可以包含实例方法而接口不能。 

  B.接口支持回调抽象类不能 。

  C.子类实现抽象基类方法必须用override关键字而实现接口方法则不用override关键字。

      D 抽象类表示是一个(IS-A)关系的抽象,接口表示(CAN-DO)关系的抽象。

      3 抽象类中的抽象方法和虚方法。

      在回答这个问题之前首先来看看什么是抽象方法和虚方法?

      3.1 什么是抽象方法

      A 在抽象类中定义的方法。

      B 方法前加abstract。

      C 因为抽象方法声明不提供实际的实现,所以没有方法体;方法声明只是以一个分号结束,并且在签名后没有大括号 ({ }),例如:public abstract void TestMethod();     

      3.2 什么是虚方法
  A.用关键字virtual声明的方法叫虚方法 。

  B.虚方法可以只是表明可以被重写 。

  C.虚方法可以被重新也可以不被重写 

  D.虚方法包含方法声明和方法体,即使方法体为空,也要加上({}) ,例如:protected virtual void TestMethod(object sender, EventArgs e) {}。

  E. 静态方法不能声明为虚方法,也不能被重写。

      通过上面的介绍我们对C#中抽象类的基本知识点有了一个大致的了解,那么我们就结合ArcGIS中的具体项目需求来谈谈对抽象类的理解。C#中类的继承和多态性能够在一定的程度上减少代码的重复,而且通过子类重写基类的虚方法或者抽象方法最终实现类的多态性,所以在一定程度上通过定义抽象的基类能够大量减少重复代码,同时也使代码的结构更加清晰明了,这也是我们写代码最终追求的东西。

  在我们的GIS项目中有一类业务特别适合使用抽象类来实现,在GIS中我们不可避免要进行各种空间查询和要素查询,比如通过框选、圈选、多边形选择......等来查询地图上面的一些要素,并且最终展示这些图层要素,首先我们通过一张图片对这一方面有个大致的了解。

技术分享

  上面的项目是针对公安行业做的一个只能GIS可视化平台,在地图上我们可以增加不同的业务图层,比如:摄像机、警车、警情、单兵、红绿灯、诱导屏等图层,这里我只是就里面的很小的一个部分来展开,就是地图查询,其实通过分析具体的业务查询,这里使用抽象基类,后面的每一种都是一个子类,这样能够减少大量重复的代码,比如这里面核心的设计到当前的地图Map,图层GraphicLayer,以及最终在图层上展示的形状Graphic,甚至是当前图层线的样式其实都是可以放到一个抽象类中的,其实定义抽象类最大的好处出了规范子类行为外,最重要的是能够实现代码的重用。

      下面我给出这一系类的代码的重用的一个基类GridButton的部分代码,然后再一步步去分析!

 public abstract partial class GridButton : Grid
    {
        protected Draw _drawSelection = new Draw();

        protected Graphic _bufferedGraphic = null; 

        public bool IsPress { set; get; }
        string _strName;
        public GridButton(string strContent, string strName)
        {
            InitializeComponent();

            _buttonId = Guid.NewGuid().ToString();

            lbl.Content = strContent;
            _strName = strName;
            imgIcon.Source = new BitmapImage(new Uri("/Source/Image/" + strName + ".png", UriKind.Relative));          
            NotPressStyle();
        }

        private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (IsPress)
            {
                NotPressStyle();
            }
            else
            {                
                PressStyle();
            }           
            
        }       
              
        private void Grid_MouseEnter(object sender, MouseEventArgs e)
        {
            img.Source = new BitmapImage(new Uri("/Source/Image/GridTitleCN.png", UriKind.Relative));
            
            this.Cursor = Cursors.Hand;
        }

        private void Grid_MouseLeave(object sender, MouseEventArgs e)
        {
            if (IsPress)
            {
                img.Source = new BitmapImage(new Uri("/Assets/Images/GridTitleCN.png", UriKind.Relative));
            }
            else
            {
                img.Source = new BitmapImage(new Uri("/Assets/Images/GridTitleC.png", UriKind.Relative));
            }
            this.Cursor = Cursors.Arrow;
        }

        /// <summary>
        /// 没有按下样式
        /// </summary>
        public void NotPressStyle()
        {
            IsPress = false;
            img.Source = new BitmapImage(new Uri("/Assets/Images/GridTitleC.png.png", UriKind.Relative));
        }

        /// <summary>
        /// 按下样式
        /// </summary>
        void PressStyle()
        {
            Grid grid = this.Parent as Grid;
            for (int i = 0; i <= grid.Children.Count - 1; i++)
            {
                if (grid.Children[i] != this)
                {
                    if ((grid.Children[i] as GridButton) != null) (grid.Children[i] as GridButton).NotPressStyle();
                    if ((grid.Children[i] as GridButtonL) != null) (grid.Children[i] as GridButtonL).NotPressStyle();
                    if ((grid.Children[i] as GridButtonR) != null) (grid.Children[i] as GridButtonR).NotPressStyle();
                }
            }
            IsPress = true;
            img.Source = new BitmapImage(new Uri("/Assets/Images/GridTitleCN.png", UriKind.Relative));
        }


        public virtual void Activate(Map map)
        {
            _activated = true;

            List<GridButton> funButtons = FrameworkElementOperate.GetChildObjects<GridButton>(this.GetParentWindow(this), "");
            for (int i = 0; i <= funButtons.Count - 1; i++)
            {
                IFunctionButton funButton = funButtons[i] as IFunctionButton;
                if (funButton != null) funButton.DeActivate();
            }

            List<GridButtonL> funLButtons = FrameworkElementOperate.GetChildObjects<GridButtonL>(this.GetParentWindow(this), "");
            for (int i = 0; i <= funLButtons.Count - 1; i++)
            {
                IFunctionButton funButton = funButtons[i] as IFunctionButton;
                if (funButton != null) funButton.DeActivate();
            }

            List<GridButtonR> funRButtons = FrameworkElementOperate.GetChildObjects<GridButtonR>(this.GetParentWindow(this), "");
            for (int i = 0; i <= funRButtons.Count - 1; i++)
            {
                IFunctionButton funButton = funButtons[i] as IFunctionButton;
                if (funButton != null) funButton.DeActivate();
            }
            _drawSelection.Map = map;
           
            _drawSelection.FillSymbol = this.FillSymbol;
            _drawSelection.LineSymbol = this.LineSymbol;
            _drawSelection.DrawBegin -= _drawSelection_DrawBegin;
            _drawSelection.DrawComplete -= _drawSelection_DrawComplete;
            _drawSelection.DrawBegin += _drawSelection_DrawBegin;
            _drawSelection.DrawComplete += _drawSelection_DrawComplete;

            _drawSelection.IsEnabled = true;
        }       

        public virtual void DeActivate()
        {
            _activated = false;
            if (this._drawSelection != null) _drawSelection.IsEnabled = false;
            if (_bufferedGraphic != null)
            {
                Map map = FrameworkElementOperate.GetChildObject<Map>(this.GetParentWindow(this), "");
                BufferedLyr bufferedLyr = map.Layers["BufferedLyr"] as BufferedLyr;
                if (bufferedLyr != null)
                {
                    bufferedLyr.Graphics.Remove(_bufferedGraphic);
                    _bufferedGraphic = null;

                    #region
                    List<X.IScreenUnit> screenUnits = X.Factory.GetSDKInstance<X.IScreenManager>().GetAllUIs();
                    string token = "";
                    for (int i = 0; i <= screenUnits.Count - 1; i++)
                    {
                        if ((screenUnits[i] as DLPSimulateUnit) != null)
                        {
                            token = screenUnits[i].Token.ToString();
                            break;
                        }
                    }
            ......
                    
                    #endregion
                }
            }
        }

        private string _buttonId = Guid.NewGuid().ToString();
        public string ButtonGUID
        {
            get
            {
                return _buttonId;
            }
        }

    protected SimpleFillSymbol FillSymbol { get { SimpleFillSymbol simpleFillSymbol = new SimpleFillSymbol(); simpleFillSymbol.Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromArgb(127, 255, 0, 0)); simpleFillSymbol.BorderBrush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromArgb(31, 0, 0, 255)); simpleFillSymbol.BorderThickness = 4; return simpleFillSymbol; } } protected SimpleLineSymbol LineSymbol { get { SimpleLineSymbol lineSymbol = new SimpleLineSymbol(); lineSymbol.Color = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(255, 0, 0)); lineSymbol.Width = 4; return lineSymbol; } } protected void SendBufferedGeometry2Server(ESRI.ArcGIS.Client.Geometry.Geometry geometry) { List<X.IScreenUnit> screenUnits = X.Factory.GetSDKInstance<X.IScreenManager>().GetAllUIs(); string token = ""; for (int i = 0; i <= screenUnits.Count - 1; i++) { if ((screenUnits[i] as DLPSimulateUnit) != null) { token = screenUnits[i].Token.ToString(); break; } } ...... } protected void SearchMapGraphics(ESRI.ArcGIS.Client.Geometry.Geometry geometry) { MapSearchUtility searchUtility = new MapSearchUtility(); searchUtility.AnsyncSearchMapItems(this._drawSelection.Map, geometry, true, null); Graphic bufferedGraphic = new Graphic(); bufferedGraphic.Geometry = geometry; SimpleFillSymbol fillSymbol = new SimpleFillSymbol(); fillSymbol.Fill = this.FillSymbol.Fill; fillSymbol.BorderBrush = this.LineSymbol.Color; bufferedGraphic.Symbol = fillSymbol; BufferedLyr bufferedLyr = this._drawSelection.Map.Layers["BufferedLyr"] as BufferedLyr; if (bufferedLyr != null) { this._bufferedGraphic = bufferedGraphic; bufferedLyr.Graphics.Add(bufferedGraphic); this.SendBufferedGeometry2Server(geometry); } } protected Window GetParentWindow(DependencyObject sunElement) { while (VisualTreeHelper.GetParent(sunElement) as Window == null) { sunElement = VisualTreeHelper.GetParent(sunElement); } return VisualTreeHelper.GetParent(sunElement) as Window; } protected virtual void DrawCompleteProc(object sender, DrawEventArgs e){ } protected virtual void DrawBeginProc(object sender, EventArgs e) {} protected void _drawSelection_DrawComplete(object sender, DrawEventArgs e) { _drawSelection.DrawMode = DrawMode.None; this.DrawCompleteProc(sender, e); } protected virtual void _drawSelection_DrawBegin(object sender, EventArgs e) { if (_bufferedGraphic != null) { Map map = FrameworkElementOperate.GetChildObject<Map>(this.GetParentWindow(this), ""); BufferedLyr bufferedLyr = map.Layers["BufferedLyr"] as BufferedLyr; if (bufferedLyr != null) { bufferedLyr.Graphics.Remove(_bufferedGraphic); _bufferedGraphic = null; ......此处省略 } } DrawBeginProc(sender, e); } protected bool _activated = false; public virtual bool Activated { get { return _activated; } } }

  我们以子类的圈选这个功能为例来一步步说明这样使用的好处,首先我们来看看圈选地图要素的代码。

 internal class CircleSearchButton:GridButton,IFunctionButton
    { 
        public CircleSearchButton(string strContent, string strName)
            : base(strContent, strName)
        {
          
        }     

        #region IFunctionButton 成员              

        public override void Activate(Map map)
        {
            base.Activate(map);
           
            base._drawSelection.DrawMode = ESRI.ArcGIS.Client.DrawMode.Circle;
        }       

        protected override void DrawBeginProc(object sender, EventArgs e)
        {
            base.DrawBeginProc(sender, e);
        }

        protected override void DrawCompleteProc(object sender, DrawEventArgs e)
        {
            base.DrawCompleteProc(sender, e);
            base.SearchMapGraphics(e.Geometry);
        } 
       
        #endregion       
    }

  首先来说明一下,首先在基类GridButton中定义了一个protected Draw对象,并为当前的这个_drawSelection定义了一系列的事件,其实主要就是两种事件,DrawBegin和DrawComplete事件,这个操作在每一个子类中都是有的,所以不用每一个子类都定义这个Draw对象,那么这个在地图上开始画线和画线完毕所执行的操作是不同的,但是这些共同的操作都是可以在基类中去共同完成的,所以我们可以看到在抽象基类GridButton中定义了两个虚方法:protected virtual void DrawBeginProc(object sender, EventArgs e) {}  protected virtual void DrawCompleteProc(object sender, DrawEventArgs e){ },我们可以看到在基类中画线完毕后的操作:

 protected void _drawSelection_DrawComplete(object sender, DrawEventArgs e)
     {
            _drawSelection.DrawMode = DrawMode.None;
            this.DrawCompleteProc(sender, e);
     }

  画线完毕后设置当前DrawMode 为 DrawMode.None,即结束画线状态,然后再执行虚方法this.DrawCompleteProc(sender, e);子类通过重载基类的这个方法来获取画线完毕之后的操作。

   我们来看看圈选查询是如何重载这个虚函数的? 

 protected override void DrawCompleteProc(object sender, DrawEventArgs e)
        {
            base.DrawCompleteProc(sender, e);
            base.SearchMapGraphics(e.Geometry);
        }   

  子类重载这个虚函数,然后执行父类的SearchMapGraphics,即查找当前e.Geometry这个范围内的所有图层内的元素,甚至查询完毕子类都是可以共用父类的SearchMapGraphics这个方法,关于查找之后是如何进行展示这里就不再一一说明了。这里只是介绍如何从业务的角度去抽取抽象基类,然后父类定义虚方法,子类去重写基类的虚方法最终实现类的多态。

  其它的查询子函数中也是通过类似的方式去实现的,比如说多边形查询:

internal class PolygonSearchButton : GridButton, IFunctionButton
    {
        public PolygonSearchButton(string strContent, string strName)
            : base(strContent, strName)
        {
           
        }
      
        public override void Activate(Map map)
        {
            base.Activate(map);
            base._drawSelection.DrawMode = ESRI.ArcGIS.Client.DrawMode.Polygon;
        }
        protected override void DrawBeginProc(object sender, EventArgs e)
        {
            base.DrawBeginProc(sender, e);
        }

        protected override void DrawCompleteProc(object sender, DrawEventArgs e)
        {
            base.DrawCompleteProc(sender, e);
            base.SearchMapGraphics(e.Geometry);
        } 
    }

  我们可以看到通过这种形式能够最大程度上减少代码,而且然代码更加合理,另外在GIS中我们知道图层是最重要的概念,我们也可以通过定义GraphicLayer的基类来让不同的图层来实现代码的共用和优化,比如下面的代码。

public abstract class BussinessGraphicLayer : GraphicsLayer
    {
       public abstract void InitialData();
       public abstract void DeInitialData();

       public virtual void SearchMapElementsAnsys(ESRI.ArcGIS.Client.Geometry.Geometry geometry, Action<List<MapElement.BussinessMapElement>> searchCompletedCallback)
       {

           LocalGeometryService.GetServiceAsync(localGeometryService =>
           {
               GeometryService geometryService = new GeometryService(localGeometryService.UrlGeometryService);
               geometryService.IntersectCompleted += new EventHandler<GraphicsEventArgs>(delegate(object obj, GraphicsEventArgs args)
               {
                   IList<Graphic> graphics = args.Results;
                   List<MapElement.BussinessMapElement> mapElements = new List<BussinessMapElement>();
                   for (int i = 0; i <= graphics.Count - 1; i++)
                   {
                       MapPoint intersectPoint = graphics[i].Geometry as MapPoint;
                       if (!double.IsNaN(intersectPoint.X))
                       {
                           mapElements.Add(this.Graphics[i] as BussinessMapElement);
                       }
                   }
                   if (searchCompletedCallback != null) searchCompletedCallback(mapElements);

               });

               if ((geometry as Envelope) != null)
               {
                   Envelope env = geometry as Envelope;

                   ESRI.ArcGIS.Client.Geometry.Polygon polygon = new Polygon();
                   polygon.SpatialReference = new SpatialReference(102100);
                   ObservableCollection<PointCollection> rings = new ObservableCollection<PointCollection>();

                   PointCollection ptCollection = new PointCollection();

                   ptCollection.Add(new MapPoint(env.XMin, env.YMin, new SpatialReference(102100)));
                   ptCollection.Add(new MapPoint(env.XMax, env.YMin, new SpatialReference(102100)));
                   ptCollection.Add(new MapPoint(env.XMax, env.YMax, new SpatialReference(102100)));
                   ptCollection.Add(new MapPoint(env.XMin, env.YMax, new SpatialReference(102100)));
                   ptCollection.Add(new MapPoint(env.XMin, env.YMin, new SpatialReference(102100)));
                   rings.Add(ptCollection);
                   polygon.Rings = rings;
                   geometry = polygon;

               }
               geometryService.IntersectAsync(this.Graphics, geometry);
           });
       }


       protected int MaxLevel
       {
           get
           {
               TiledLayer tpkLayer = this.Map.Layers["BaseMap"] as TiledLayer;
               int level = 0;
               double temp = 90000000000;

               for (int i = 0; i <= tpkLayer.TileInfo.Lods.Length - 1; i++)
               {
                   double delta = Math.Abs(tpkLayer.TileInfo.Lods[i].Resolution - this.Map.MinimumResolution);
                   if (delta < temp)
                   {
                       level = i;
                       temp = delta;
                   }
               }
               return level;
           }
       }


       protected int MinLevel
       {
           get
           {
               TiledLayer tpkLayer = this.Map.Layers["BaseMap"] as TiledLayer;
               double mapResolution = this.Map.Resolution;
               int level = 0;
               double temp = 90000000000;

               for (int i = 0; i <= tpkLayer.TileInfo.Lods.Length - 1; i++)
               {
                   double delta = Math.Abs(tpkLayer.TileInfo.Lods[i].Resolution - this.Map.MaximumResolution);
                   if (delta < temp)
                   {
                       level = i;
                       temp = delta;
                   }
               }
               return level;
           }
       }

       protected int ZoomLevel
       {
           get
           {
               int level = 0;
               try
               {
                   TiledLayer tpkLayer = this.Map.Layers["BaseMap"] as TiledLayer;
                   double mapResolution = this.Map.Resolution;
                   
                   double temp = 90000000000;
                   int index0 = 0;
                   int index1 = tpkLayer.TileInfo.Lods.Length - 1;

                   for (int i = 0; i <= tpkLayer.TileInfo.Lods.Length - 1; i++)
                   {
                       double delta = Math.Abs(tpkLayer.TileInfo.Lods[i].Resolution - this.Map.MinimumResolution);
                       if (delta < temp)
                       {
                           index1 = i;
                           temp = delta;
                       }
                   }

                   temp = 90000000000;
                   for (int i = 0; i <= tpkLayer.TileInfo.Lods.Length - 1; i++)
                   {
                       double delta = Math.Abs(tpkLayer.TileInfo.Lods[i].Resolution - this.Map.MaximumResolution);
                       if (delta < temp)
                       {
                           index0 = i;
                           temp = delta;
                       }
                   }

                   temp = 90000000000;
                   for (int i = index0; i <= index1; i++)
                   {
                       double delta = Math.Abs(tpkLayer.TileInfo.Lods[i].Resolution - mapResolution);
                       if (delta < temp)
                       {
                           level = i;
                           temp = delta;
                       }
                   }
               }
               catch (Exception ex)
               {
                   return 0;
               }
               return level;
           }
       }

       public Map MyMap
       {
           get
           {
               return this.Map;
           }
       }       
    }

  我们我们可以把很多共同的东西放到这些基类中,比如当一个图层加载后我们通过定public abstract void InitialData();这个抽象方法让子类去重写,从而实现从不同的地方加载业务图层数据,但是对于每个图层如何通过异步回调去查询元素则是都可以通过基类中的SearchMapElementsAnsys这个异步函数来实现的,当然关于抽象类有太多的东西需要去总结,这样才能够真正地去掌握这些知识的精妙之处......

结合具体项目谈谈对抽象类的理解