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

时间:2010-02-14 01:28:06

标签: c# class oop interface

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

然而,我一直在努力这样做。不要误会我的意思,我知道关于 OO设计及其含义,我只是不知道如何在项目中实施良好的OO设计。当然,您可以轻松查看您在书中或在线阅读的课程示例。示例可能包含以下简单方案。

接口IPerson 具有成员函数 Walk() Run()抽象类人使用 IPerson接口 Class Man Class Female 继承自抽象类人

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

接口:

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;
    }

}

4 个答案:

答案 0 :(得分:7)

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

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

所以,不是有一个IPicture,我会把它变成'IResizableImage'(读取你的规格,就是这样。不只是一张图片,而是一张可调整大小的图片) 而不是'ReturnImage()'我将使用像'Scale()'这样的东西。 '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的用户。是否感觉自然而且容易获取?

希望这有帮助,

答案 1 :(得分:6)

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

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

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

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

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

  • 只有一张图片,而名字则表示许多图片。
  • 它包含“我的”,在我看来是不正确的,因为如果我读了你的代码不是我的班级。这是你的;)

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

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

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)
    }
}

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

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

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

这就是现在。希望这些信息对您有所帮助。

答案 2 :(得分:0)

要实现良好的设计,您需要应用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。

答案 3 :(得分:0)

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

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. 获取初始位图,并将最终结果写出来。