C#:迷你应用程序结构设计(类/接口/等)

con*_*att 4 c# oop interface class

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

但是,我一直在努力这样做.不要误会我的意思,我知道关于面向对象的设计,它是什么,我只是不知道如何实现在项目良好的面向对象设计.当然,您可以轻松查看您在书中或在线阅读的课程示例.示例可以具有诸如以下的简单场景.

接口IPerson具有成员函数Walk(),Run(). 抽象类人使用IPerson接口. 人和类女继承自抽象类人.

但是当谈到Real Projects时,我很难实现好的设计.我希望有一些见解.这是我现在拥有的.

接口:

interface IPicture
{
    Bitmap ReturnImage(string path, int width, int height);
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

主类; 申请的起点.这绝对需要一些工作......

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;
    }
Run Code Online (Sandbox Code Playgroud)

}

Mar*_*mit 7

扫描完代码后,是的,它是精心设计的...也许有点多;)

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

因此,我没有使用IPicture,而是使它成为"IResizableImage"(读取您的规范,就是它的原因.不只是一张图片,而是一张可调整大小的图片)而不是'ReturnImage()'我会使用像'规模.'ImageSave()'到'Save()'

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

IResizableImage myImg = new ResizableImage( orignalBitmap );
Image rescaledImg = myImg.Scale( "new path", 320,240 );
resccaledImg.Save();

代替:

IPicture myImg = new MyPictures();
Image rescaled = myImg.ReturnImage( "newpath", 320, 240 );
rescaledImg.ImageSave();

所以,一般来说,类是名词,方法是动词,而adjetives是属性/字段.尽量减少重复或重复."ImageSave"是Image上的一种方法."Image.Save()"不是"Image.ImageSave()"更清晰吗?

只是我的一些想法; 在编码指南中没有绝对的对错.在使用API​​与编写API时,可以考虑成为另一个人.跳出"我知道它做了什么"的框,并想象成为以前从未见过这个API的用户.它是否自然而且易于使用?

希望这可以帮助,


Vic*_*aci 6

以下是代码和设计的一些改进.这些提示并非与OO相关,但您应该意识到良好的设计不仅仅是OO设计.

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

//Declare variables to hold values that have been determined
private int _OldWidth;
Run Code Online (Sandbox Code Playgroud)

这个评论是多余的,因为任何程序员都会理解这是一个声明.

2.避免给出错误的名字.例如,"MyPictures"类不是很正确,因为:

  • 只持有一张照片,而名字则暗示很多照片.
  • 它包含"我的",在我看来是不正确的,因为如果我读你的代码不是我的班级.这是你的 ;)

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

bmp.Save(_NewImgPath + @"\" + _NewImgName + "." +  _NewImgFormat.ToString().ToLower(), _NewImgFormat);
Run Code Online (Sandbox Code Playgroud)

4.保持方法简短.很难将所有方法保留为5行代码,但是对于ProcessFiles来说,30行(如果我的计数是正确的 - 没有注释和空行)有点太多了.

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

6.正如其他人已经提到的那样,尝试将演示文稿(图形用户界面 - GUI)与逻辑分开.使其成为可以在不更改逻辑代码的情况下替换GUI的方式.

7.承担单一责任职能.btnMoveOne_Click有多个职责:它检查文件是否存在,它处理用户界面上的元素.

8.您的图像类与文件系统耦合.如果我想存储在内存中创建的图像会发生什么?那么路径是什么?您可以在这里改进课程设计.如果文件来自磁盘(HINT:在FileStream中)或从内存(HINT:在MemoryStream中)或任何其他位置,这样做无关紧要.

目前为止就这样了.希望这些信息对您有所帮助.