【问题标题】:C# : Mini Application Structural Design (Classes/Interfaces/etc.)C#:迷你应用程序结构设计(类/接口/等)
【发布时间】:2010-02-14 01:28:05
【问题描述】:

我一直在创建一个允许用户将图像转换为各种尺寸和格式的小型应用程序。我一直在努力通过这个应用程序获得良好的可靠设计。我已经启动并运行了应用程序,但它确实集成了良好的面向对象设计。由于这是一个个人项目,我一直想了解更多关于集成接口、良好的类继承、对象组合和其他面向对象设计元素的知识。

但是,我一直在努力做到这一点。不要误会我的意思,我知道关于 OO 设计以及它是什么,我只是不知道如何在项目中实施 好的 OO 设计。当然,它很容易查看您在书籍或在线阅读的课程示例。示例可能有如下简单场景。

Interface IPerson 有成员函数 Walk(), Run()抽象类人使用IPerson接口Class ManClass Female 继承自 Abstract Class Person

但是当涉及到真实项目时,我很难实现好的设计。我希望有一些见解。这是我目前拥有的。

界面:

interface IPicture
{
    Bitmap ReturnImage(string path, int width, int height);
}

保存图片信息的主类。此类主要存储有关传递的图像的信息,以及有关用户想要的新值的信息(即新大小、新文件位置、新图片格式等)

public class MyPictures : IPicture
{
    //All Private variables below are properties.  Property get/set's have been removed
    //for the sake of space
    private int _NewWidth;
    private int _NewHeight;
    private string _NewImgName;
    private string _NewImgPath;
    private string _NewImgFullPath;
    private ImageFormat _NewImgFormat;
    //Declare variables to hold values that have been determined
    private int _OldWidth;
    private int _OldHeight;
    private string _OldImgName;
    private string _OldImgPath;
    //Old Image Format is in String format because of certain extension scenarios.
    private string _OldImgFormat;

         public MyPictures(Image img, string file)
    {
        ClearProperties();
        //...set properties based on passed variables in constructor...
    }
    public void ClearProperties()
    {
        _NewWidth = 0;
        _NewHeight = 0;
        _NewImgName = "";
        _NewImgPath = "";
        _NewImgFullPath = "";
        _NewImgFormat = null;
        _OldWidth = 0;
        _OldHeight = 0;
        _OldImgName = "";
        _OldImgPath = "";
        _OldImgFormat = null;
    }

    public override string ToString()
    {  
        return _OldImgPath;
    }
    public void ImageSave()
    {
        Bitmap tempBmp = new Bitmap(_OldImgPath);
        Bitmap bmp = new Bitmap(tempBmp, _NewWidth, _NewHeight);
        bmp.Save(_NewImgPath + @"\" + _NewImgName + "." +  _NewImgFormat.ToString().ToLower(), _NewImgFormat);
    }
    public Bitmap ImageClone()
    {
        Bitmap bmp = new Bitmap(_OldImgPath);
        return bmp;
    }
    Bitmap IPicture.ReturnImage(string path, int width, int height)
    {
        return new Bitmap(new Bitmap(path), width, height);
    }
}

主类;申请的起点。这肯定需要一些工作......

public partial class Form1 : Form
{
    static bool hasThreadBeenStopped = false;
    static bool imageProcessingComplete = false;
    static bool imgConstrained = false;
    //Default text when user selects 'All' checkbox for new image name
    static string newNameDefault = "'Name' + #";
    Utility.Validation.Validate valid = new Utility.Validation.Validate();

    public Form1()
    {
        InitializeComponent();
        //Populate Combo Box With Possible Image Formats...
        //Conditionally show Image Properties...
        ImgPropertiesEnabled();
        //Set static progress bar properties...
        progressBar1.Minimum = 0;
        progressBar1.Step = 1;
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        lblImgProcessed.Text = "";
        lblFile.Text = "";
        txtContentFolder.Text = "";
    }
    //Delegate declarations.  Used for multi-thread processing
    public delegate void PopulateTextboxDelegate(Label lbl, string text);
    public delegate void ThreadWorkDelegate(Label lbl, string text);
    public delegate void ImageDisplayDelegate(Image i);
    public delegate void ProgressBarDelegate(ProgressBar p, int step, int value);

    //Populate textbox fields with image processed, and image path being processed
    public void PopulateTextbox(Label lbl, string text)
    {
        lbl.Text = "";
        lbl.Text = text;
    }
    public void ThreadWork(Label lbl, string text)
    {
        this.Invoke(new PopulateTextboxDelegate(PopulateTextbox),
                    new object[] { lbl, text });
    }
    //Display Currently Processed Image
    public void ImageDisplay(Image i)
    {
        pbMain.Image = null;
        pbMain.Image = i;
    }
    public void ThreadWorkImg(Image i)
    {
        this.Invoke(new ImageDisplayDelegate(ImageDisplay),
                    new object[] {i});
    }
    //Increment Progress Bar
    public void ProgressBarDisplay(ProgressBar pg, int max, int value)
    {
        //Dynamically set the Progress Bar properties
        pg.Maximum = max;
        pg.Value = value;
    }
    public void ThreadProgress(ProgressBar p, int max, int value)
    {
        this.Invoke(new ProgressBarDelegate(ProgressBarDisplay),
                    new object[] { p, max, value });
    }        
    private void btnStart_Click(object sender, EventArgs e)
    {
        string IsValidResult = IsValid();
        //If string is empty, Utility passed
        if (IsValidResult == "")
        {
            Thread t = new Thread(new ThreadStart(ProcessFiles));
            t.Start();
        }
        else
        {
            MessageBox.Show(IsValidResult);
        }
    }
    public void ProcessFiles()
    {
        int count = 0;

        ThreadWorkDelegate w = ThreadWork;
        ImageDisplayDelegate im = ThreadWorkImg;
        ProgressBarDelegate pb = ThreadProgress;

        try
        {
            foreach (MyPictures mp in lstHold.Items)
            {
                try
                {
                    if (hasThreadBeenStopped == false)
                    {
                        //Disable certain controls during process.  We will use the generic
                        //MethodInvoker, which Represents a delegate that can execute any method 
                        //in managed code that is declared void and takes no parameters.
                        //Using the MethodInvoker is good when simple delegates are needed.  Ironically,
                        //this way of multi-thread delegation was used because the traditional way as used
                        //by the rest of the delegates in this method, was not working.
                        btnApply.Invoke(new MethodInvoker(delegate { btnApply.Enabled = false; }));
                        btnStart.Invoke(new MethodInvoker(delegate { btnStart.Enabled = false; }));

                        //Call delegate to show current picture being processed
                        im.BeginInvoke(mp.ImageClone(), null, null);
                        mp.ImageSave();

                        //Increment Count; Image has been processed
                        count++;

                        //Invoke Img Proceessed Output
                        w.BeginInvoke(lblImgProcessed, count.ToString() +
                                      " of " + lstHold.Items.Count.ToString() + " processed",
                                      null, null);
                        //Invoke File Process Output
                        w.BeginInvoke(lblFile, mp.NewImgPath, null, null);

                        //Invoke Progressbar output.  Delegate is passed The count of images,
                        //which will be set as the progressbar max value.  the 'count' variable is
                        //passed to determine the current value.
                        pb.BeginInvoke(progressBar1, lstHold.Items.Count, count, null, null);
                    }
                    else //Thread has been called to stop
                    {
                        MessageBox.Show("Image Processing Stopped: " + count + "of " +
                                        lstHold.Items.Count + " processed");
                        //Enable controls after process
                        btnApply.Invoke(new MethodInvoker(delegate { btnApply.Enabled = true; }));
                        btnStart.Invoke(new MethodInvoker(delegate { btnStart.Enabled = true; }));
                        break;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error while processing pictures");
                    break;
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error while attempting to execute pictures: " + ex.ToString());
        }
        finally
        {
            //Loop has ended:
            //In finally statement, re-enable disabled controls
            //Enable certain controls during process
            btnApply.Invoke(new MethodInvoker(delegate { btnApply.Enabled = true; }));
            btnStart.Invoke(new MethodInvoker(delegate { btnStart.Enabled = true; }));
            //Reset class variables
            hasThreadBeenStopped = false;
            imageProcessingComplete = false;
        }
    }

    private void btnContent_Click(object sender, EventArgs e)
    {
        string selection = null;
        string[] files = null;

        lstAll.Items.Clear();
        contentBrowser.ShowDialog();

        selection = contentBrowser.SelectedPath;
        txtContentFolder.Text = selection;
        if (selection != "" || selection != null)
        {
            try
            {
                files = System.IO.Directory.GetFiles(selection.Trim());
                foreach (string file in files)
                {
                    lstAll.Items.Add(file);
                }
            }
            catch (Exception ex)
            {
               // MessageBox.Show(ex.ToString());
            }

        }
    }

    private void btnGo_Click(object sender, EventArgs e)
    {
        //Grab files from folder based on user input in the textbox.  
        string selection = txtContentFolder.Text.Trim();
        string[] files = null;

        lstAll.Items.Clear();

        if (valid.IsNull(selection) == false || valid.IsEmpty(selection) == false)
        {
            try
            {
                files = System.IO.Directory.GetFiles(selection);
                foreach (string file in files)
                {
                    lstAll.Items.Add(file);
                }
            }
            catch (Exception ex)
            {
               MessageBox.Show("Invalid Directory");
            }
        }
        txtContentFolder.Text = selection;
    }
    private void btnDestination_Click(object sender, EventArgs e)
    {
        string selection = null;
        destinationBrowser.ShowDialog();
        selection = destinationBrowser.SelectedPath;
        txtNewImgPath.Text = selection;
    }

    private void exitToolStripMenuItem_Click(object sender, EventArgs e)
    {
        this.Close();
    }

    private void btnStop_Click(object sender, EventArgs e)
    {
        //Flag variable that the stop button has been called.  This variable is checked
        //conditionally when looping over each picture.
        hasThreadBeenStopped = true;
    }

    public string IsValid()
    { 
        StringBuilder sb = new StringBuilder("");

        if (lstHold.Items.Count <= 0)
        {
            return "No items exist to process";
        }
        //Validate that there is a value in each field for every object in lstHold.  All the fields will be
        //validated.  Note:  If there is one invalid field, the rest do not need to be considered.  
        foreach (MyPictures mp in lstHold.Items)
        {
            if (mp.NewImgName == "")
            {
                sb.Append(mp.OldImgPath + ", ");
            }
            else if (mp.NewImgPath == "")
            {
                sb.Append(mp.OldImgPath + ", ");
            }
            else if (mp.NewImgFormat == null)
            {
                sb.Append(mp.OldImgPath + ", ");
            }
            else if (mp.NewWidth == 0)
            {
                sb.Append(mp.OldImgPath + ", ");
            }
            else if (mp.NewHeight == 0)
            {
                sb.Append(mp.OldImgPath + ", ");
            }
        }
        //If the returned string is empty, the image is valid.  The check for the listbox's count
        //will return a string immediatly if false.  Because of this, we know that the returning
        //string at this level will either be empty (validation passed) or filled with image paths
        //of images missing required values.  If image is not valid, return this concatenated string of image paths
        //that are missing values, and insert a prefixed string literal to this list.
        if (sb.ToString() != "")
        {
            sb.Insert(0, "The following images are missing required values: ");
            return sb.ToString();
        }
        else //String is empty and has passed validation
        {
            return sb.ToString();
        }

    }

    private void btnMoveOne_Click(object sender, EventArgs e)
    {
        //Loop through All strings in the lstAll list box.  Then use each picture path to convert 
        //each picture into their own class
        foreach (string file in lstAll.SelectedItems)
        {
            //isImgExistFlag is a flag indicating wheter the image coming from lstAll already exists
            //in lstHold.  By default, the variable is false.  It is set to true if an image does exist
            //This variable must be re-created within the scope of the main foreach loop to ensure a proper
            //reset of the variable for each image comparison.
            bool isImgExistFlag = false;
            try
            {
                Image img;
                img = Image.FromFile(file);

                MyPictures mp = new MyPictures(img,file);

                //If lstHold contains no items, add the item with no validation check.  
                if (lstHold.Items.Count == 0)
                {
                    lstHold.Items.Add(mp);
                }
                else
                {
                    //Run through each object in the lstHold to determine if the newly created object
                    //already exists in list box lstHold.
                    for (int i = 0; i < lstHold.Items.Count; i++)
                    {
                        MyPictures p = (MyPictures)lstHold.Items[i];
                        //Unique objects will be identified by their Original Image Path, because
                        //this value will be unique
                        if (p.OldImgPath == mp.OldImgPath)
                        {
                            isImgExistFlag = true;
                        }
                    }
                    //If isImgExistFlag is false, the current Image object doesnt currently exist 
                    //in list box.  Therefore, add it to the list.  
                    if (isImgExistFlag == false)
                    {
                        lstHold.Items.Add(mp);
                    }
                }

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }                    
        }
      }

    private void btnMoveAll_Click(object sender, EventArgs e)
    {
        //This event has the same functionality as btnMoveOne_Click, except the main foreach loop
        //is based on all of lstAll's items, rather than just the selected items.
        foreach (string file in lstAll.Items)
        {
            bool isImgExistFlag = false;
            try
            {
                Image img;
                img = Image.FromFile(file);

                MyPictures mp = new MyPictures(img, file);

                if (lstHold.Items.Count == 0)
                {
                    lstHold.Items.Add(mp);
                }
                else
                {
                    for (int i = 0; i < lstHold.Items.Count; i++)
                    {
                        MyPictures p = (MyPictures)lstHold.Items[i];

                        if (p.OldImgPath == mp.OldImgPath)
                        {
                            isImgExistFlag = true;
                        }
                    }
                    if (isImgExistFlag == false)
                    {
                        lstHold.Items.Add(mp);
                    }
                }

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
    }

    private void btnRemoveOne_Click(object sender, EventArgs e)
    {
        /*
        Create a seperate List to populate:
        This is necessary because if you explicitly remove an item from the listbox
        you will get the following error:
        "List that this enumerator is bound to has been modified. An enumerator can 
        only be used if the list does not change."
         */
        //This variable will keep track of the first index processed. 
        int first_index = 0;
        int count = 0;
        List<MyPictures> TempMp = new List<MyPictures>();

        if (lstHold.Items.Count >= 1)
        {
            try
            {
                foreach (MyPictures mp in lstHold.SelectedItems)
                {
                    if (count == 0)
                    {
                        first_index = lstHold.SelectedIndex;
                    }
                    //Add objects to be removed
                    TempMp.Add(mp);
                }
                foreach (MyPictures mp2 in TempMp)
                {
                    lstHold.Items.Remove(mp2);
                }
            }
            catch (Exception ex)
            {
                //Hide Error: MessageBox.Show(ex.ToString());
            }
            //Select new item in list if possible, as long as there is a item in the list
            if (lstHold.Items.Count >= 1)
            {
                //If the first_index variable = the amount of items in the list, the new selected index
                //should be the first index -1.  This is because the variable first_index would be the 
                //index of the now deleted item in the list.  Therefore we must subtract the variable by 1 
                //before assigning it to the selected value.  Otherwise, we'll be assigning a selected index that
                //no longer exists. 
                //There is also a check to make sure there is more than one item in the list.  Otherwise, we could
                //potentially assign a selected index of -1.
                if (first_index == lstHold.Items.Count && lstHold.Items.Count != 1)
                {
                    lstHold.SelectedIndex = first_index - 1;
                }
                else if (lstHold.Items.Count == 1)
                {
                    lstHold.SelectedIndex = 0;
                }
                else
                {
                    lstHold.SelectedIndex = first_index;
                }
            }
            else
            {
                ClearTextBoxes();
            }
        }

    }

    private void btnRemoveAll_Click(object sender, EventArgs e)
    {
        lstHold.Items.Clear();
        ClearTextBoxes();
        ImgPropertiesEnabled();
    }

    private void lstHold_SelectedIndexChanged(object sender, EventArgs e)
    {
        //This prevents trying to access a negative index.  This can happen when a item is removed.
        if (lstHold.SelectedIndex >= 0)
        {
            try
            {
                MyPictures mp = (MyPictures)lstHold.Items[lstHold.SelectedIndex];
                txtOldName.Text = mp.OldImgName;
                txtOldImgPath.Text = mp.OldImgPath;
                txtOldImgFormat.Text = mp.OldImgFormat.ToString();
                txtOldWidth.Text = mp.OldWidth.ToString();
                txtOldHeight.Text = mp.OldHeight.ToString();

                txtNewName.Text = mp.NewImgName;
                cbFormat.SelectedItem = mp.NewImgFormat;
                txtNewWidth.Text = mp.NewWidth.ToString();
                txtNewHeight.Text = mp.NewHeight.ToString();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
        //Call function to determine which controls should be enabled/disabled
        ImgPropertiesEnabled();
    }

    private void btnApply_Click(object sender, EventArgs e)
    {

        //Reset color.  It could be grey depending on if user changed default name.
        txtNewName.ForeColor = Color.Black;

        if (lstHold.SelectedIndex == -1)
        {
            MessageBox.Show("Picture not selected.  Select picture to apply properties to.");
        }
        else if (lstHold.SelectedIndex >= 0)
        {
            MyPictures mp = (MyPictures)lstHold.Items[lstHold.SelectedIndex];

            //User wants to apply a generated name to all pictures within the list
            if (chkNewPicName.Checked == true)
            {
                int count = 0;
                foreach (MyPictures pic in lstHold.Items)
                {
                    pic.NewImgName = txtNewName.Text + count.ToString();
                    ++count;
                }
                txtNewName.Text = mp.NewImgName;
            }
            //User wants to apply a custom name to this picture only
            else
            {
                mp.NewImgName = txtNewName.Text;
            }
            //User wants to apply this path to all pictures within the list
            if (chkNewPicPath.Checked == true)
            {
                foreach (MyPictures pic in lstHold.Items)
                {
                    pic.NewImgPath = txtNewImgPath.Text;
                }
                txtNewImgPath.Text = mp.NewImgPath;
            }
            //User wants to apply this path to this picture only
            else
            {
                mp.NewImgPath = txtNewImgPath.Text;
            }
            //User wants to apply this image format to all pictures within the list
            if (chkNewPicFormat.Checked == true)
            {
                foreach (MyPictures pic in lstHold.Items)
                {
                    pic.NewImgFormat = (ImageFormat)cbFormat.SelectedItem;
                }
            }
            //User wants to apply this image format to this picture only
            else
            {
                mp.NewImgFormat = (ImageFormat)cbFormat.SelectedItem;
            }
            //User wants to apply this size to all pictures 
            if (chkNewSize.Checked == true)
            {
                foreach (MyPictures pic in lstHold.Items)
                {
                    pic.NewWidth = Convert.ToInt32(txtNewWidth.Text);
                    pic.NewHeight = Convert.ToInt32(txtNewHeight.Text);
                }
                txtNewWidth.Text = mp.NewWidth.ToString();
                txtNewHeight.Text = mp.NewHeight.ToString();
            }
            //User wants to apply this size to this picture only
            else
            {
                mp.NewWidth = Convert.ToInt32(txtNewWidth.Text);
                mp.NewHeight = Convert.ToInt32(txtNewHeight.Text);
            }

            mp.NewImgName = txtNewName.Text;
            mp.NewImgFormat = (ImageFormat)cbFormat.SelectedItem;
            mp.NewWidth = Convert.ToInt32(txtNewWidth.Text);
            mp.NewHeight = Convert.ToInt32(txtNewHeight.Text);
        }
    }

    private void checkBox1_CheckedChanged(object sender, EventArgs e)
    {
        if (chkSelectAll.Checked)
        {
            chkNewPicName.Checked = true;
            chkNewPicPath.Checked = true;
            chkNewPicFormat.Checked = true;
            chkNewSize.Checked = true;
        }
        else
        {
            chkNewPicName.Checked = false;
            chkNewPicPath.Checked = false;
            chkNewPicFormat.Checked = false;
            chkNewSize.Checked = false;
        }
    }

    private void previewToolStripMenuItem_Click(object sender, EventArgs e)
    {
        MessageBox.Show("hi there!");
    }

    private void btnPreview_Click(object sender, EventArgs e)
    {
        try
        {
            if (lstHold.Items.Count <= 0)
            {
                MessageBox.Show("No pictures are available to preview");
            }
            else if (lstHold.SelectedItem == null)
            {
                MessageBox.Show("No picture is selected to preview");
            }
            else
            {
                MyPictures mp = (MyPictures)lstHold.SelectedItem;
                //Bitmap bmp = new Bitmap(mp.OldImgPath);
                Form2 frm = new Form2(mp);
                frm.Show();
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show("An Error has occured:\n " + ex.ToString());
        }
    }
    public void ImgPropertiesEnabled()
    {
        //Enable Image properties when an image is selected
        if (lstHold.SelectedIndex >= 0)
        {
            gbCheckAll.Enabled = true;
            gbImgProperties.Enabled = true;
        }
        else
        {
            //Disable Image properties when an image is not selected
            gbCheckAll.Enabled = false;
            gbImgProperties.Enabled = false;

        }
        //Preview buttons enablement will depend on the same conditions
        btnPreview.Enabled = gbImgProperties.Enabled;
    }
    public void ClearTextBoxes()
    {
        txtNewImgPath.Text = "";
        txtNewName.Text = "";
        txtNewHeight.Text = Convert.ToString(0);
        txtNewWidth.Text = Convert.ToString(0);
        cbFormat.SelectedItem = null;
        chkSelectAll.Checked = false;
    }

}

【问题讨论】:

  • 我可能是错的,但我怀疑是否有人想浏览那么多代码。 BTW,你的界面IPicture,看起来更像IBitmapImage
  • 我只是在寻找基本的OO结构方向。不深入。

标签: c# class oop interface


【解决方案1】:

扫描完代码后,是的,它很优雅……也许有点多;)

我注意到的一件事是您的命名约定。尽管它在运行时没有改变任何东西,但它确实使 API/代码维护更容易。

所以,我不会使用 IPicture,而是将其设置为“IResizableImage”(阅读您的规范,就是这样。不仅仅是一张图片,而是一个可调整大小的图片) 而不是'ReturnImage()',我会使用'Scale()'之类的东西。 'ImageSave()' 到 'Save()'

您的代码将开始读取(通过命名约定添加了符号信息)

IResizableImage myImg = new ResizableImage( orignalBitmap ); 图片 rescaledImg = myImg.Scale("新路径", 320,240); rescaledImg.Save();

代替:

IPicture myImg = new MyPictures(); 图片重新缩放 = myImg.ReturnImage("newpath", 320, 240); rescaledImg.ImageSave();

所以,一般来说,类是名词,方法是动词,形容词是属性/字段。尽量减少重复或冗余。 “ImageSave”是您的 Image 上的一种方法。 “Image.Save()”不是比“Image.ImageSave()”更清晰吗?

只是我的一些想法; 在编码指南中,没有绝对的对与错。 在使用 API 与编写 API 时,请考虑成为另一个人。跳出“我知道它做什么”的框框,想象自己是一个从未见过这个 API 的用户。是否感觉自然且易于使用?

希望这会有所帮助,

【讨论】:

    【解决方案2】:

    以下是针对您的代码和设计的一些改进。这些技巧并非都与 OO 相关,但您应该意识到好的设计不仅仅是 OO 设计。

    1.避免评论显而易见的事情。

    //Declare variables to hold values that have been determined
    private int _OldWidth;
    

    这个注释是多余的,因为任何程序员都会明白这是一个声明。

    2.避免命名错误。例如,“MyPictures”类并不正确,因为:

    • Is 仅包含一张图片,而名称暗示了许多图片。
    • 它包含“我的”,我认为这是不正确的,因为如果我阅读了您的代码,则不是我的课程。这是你的 ;)

    3.避免连接字符串。使用 string.Format 或者,对于路径,Path.Combine

    bmp.Save(_NewImgPath + @"\" + _NewImgName + "." +  _NewImgFormat.ToString().ToLower(), _NewImgFormat);
    

    4.保持方法简短。很难将所有方法保持在 5 行代码中,但 ProcessFiles 的 30 行(如果我的计数是正确的 - 没有 cmets 和空行)有点太多了。

    5.不要仅仅因为你想拥有它们而使用设计元素。我认为没有理由在您的代码中使用该接口。在您的情况下,它只会增加代码的复杂性。更何况,你还没用过(!!!)。您刚刚实现了它,仅此而已。当您有多种共享通用功能的类型(接口中的那些)并且您希望在不了解实际实现的情况下将它们都视为相似时,请使用接口。

    interface IImage
    {
        void DrawLine(Point startPoint, Point endPoint);
    }
    
    class MonochromeImage:IImage
    {
        void DrawLine(Point startPoint, Point endPoint)
        {
            //Draw a monochrome line on images with one channel
        }
    }
    
    class ColorImage:IImage
    {
        void DrawLine(Point startPoint, Point endPoint)
        {
            //Draw a red line on images with three channels
        }
    }
    
    ...
    
    void DrawLineOnImage()
    {
        List<IImage> images = new List<IImage>();
        images.Add(new ColorImage());
        images.Add(new MonochromeImage());
    
        //I am not aware of what kind of images actually have
        //all it matters is to have a draw line method
        foreach(IImage image in images)
        {
            image.DrawLine(p1,p2)
        }
    }
    

    6.正如其他人已经提到的,尝试将表示(图形用户界面 - GUI)与逻辑分开。以这样一种方式制作,您可以在不更改逻辑代码的情况下替换 GUI。

    7.制作单一职责功能。 btnMoveOne_Click 的职责不止一项:它检查文件是否存在并处理用户界面上的元素。

    8.你的图像类是耦合到文件系统的。如果我想将创建的图像存储在内存中会发生什么?那么路径是什么呢?在这里您可以改进课程的设计。以这样一种方式制作,无论文件来自磁盘(提示:在 FileStream 中)还是来自内存(提示:在 MemoryStream 中)或任何其他地方。

    暂时就这些了。希望这些信息对您有所帮助。

    【讨论】:

      【解决方案3】:

      要实现好的设计,您需要应用 TDD(测试驱动设计)。

      您很快就会发现,可测试性需要将项目分层,例如表示和业务逻辑。

      开始用测试覆盖你的项目,你不会相信你会以多快的速度发现与它的设计不一致。

      事情会站起来尖叫:“你绝不会考验我!” 最糟糕的敌人是隐藏在 WinForms 中的代码。 您可以做的是使视图“谦虚”。 http://codebetter.com/blogs/jeremy.miller/archive/2007/05/23/build-your-own-cab-part-2-the-humble-dialog-box.aspx

      对于项目示例,您必须查看架构模式,而不是 OOP 示例。 您将要查找的关键字是 MVC、MVP、MVVM。

      【讨论】:

        【解决方案4】:

        好吧,这就是我要做的。它可能与许多人会做的不同,但我认为这是一个非常好的、灵活的设计。

        public abstract class AbstractConverter : IImageHandler
        {
            public AbstractConverter(IImageHandler nextHandler)
            {
                output = nextHandler;
            }
            public void HandleImage(Bitmap b)
            {
                var outBitmap = Convert(b);
                output.HandleImage(outBitmap);
            }
        
            protected abstract Bitmap Convert(Bitmap input);
        
            private IImageHandler output;
        }
        
        public interface IImageHandler
        {
            void HandleImage(Bitmap b);
        }
        

        现在,您的应用程序的其余部分是:

        1. 创建 AbstractConverter 的实现来处理您想要的单个转换
        2. 创建可以一起构建和连接转换器的东西
        3. 获取初始位图,并写出最终结果。

        【讨论】:

        • 我从未见过像您那样使用“下一个”关键字。 'next' 关键字在你如何做的上下文中是什么意思?
        • 没什么,它只是一个代表链中下一个对象的变量。现在编辑代码:)
        • 好的,现在说得通了。我认为这可能是 .NET 4.0 中的新内容,哈哈。 :)
        猜你喜欢
        • 2012-08-01
        • 1970-01-01
        • 2014-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-21
        • 1970-01-01
        相关资源
        最近更新 更多