使用簡單工廠寫一個可複用的批量檔案修改工具

謙行發表於2013-10-08

前段日子忙活站點Theme的更改,到釋出的時候有個問題難住了我,我要修改四十多個Theme的配置檔案,這些配置檔案也不在一處,整理出來打包很麻煩,而且本地環境和生產環境裡面很多配置都不相同,不能通過簡單把全部檔案貼上過去這種方式替換生產環境的配置檔案,只能去修改,後來頻繁的修改Theme,終於意識到要寫個工具了,之前也有一些修改檔案的工具,但都是各行其是,痛定思痛決定寫一個可複用的批量檔案修改工具,當然本文的重點並不在於怎麼查詢、修改檔案,而在於怎麼複用。

最開始程式設計的時候聽說過設計模式,急不可耐的讀了一番,說實話幾乎沒有收穫,不明白為什麼要用設計模式,工作兩年後再讀設計模式,不敢說收穫有多大,但是重構了很多以前的程式碼,感覺不錯,後來自己就情不自禁的在使用一些設計模式了,這個簡單的小工具,工廠模式又幫了大忙

需求

我希望做出來的效果是這樣的

image

1. 可以在某個資料夾下搜尋檔案,而不是檢索整個硬碟(這個是當然)

2. 不僅僅是按照檔案全名搜尋,還可以使用一些萬用字元

3. 能夠自己決定是否搜尋子資料夾

這些都是框架部分,至於怎麼去修改檔案,肯定只有開發者自己知道,所以這個工具還得有開發者在框架下不影響其它演算法,而擴充自己所需演算法的功能。也就是說當我想批量修改某些檔案的時候我可以僅僅寫一個如何修改檔案的演算法,而不用關注與怎麼搜尋神馬的,同時擴充部分還不能影響其它演算法。

基礎框架

需求其實很簡單,看看怎麼實現,我做的solution目錄結構是這樣的

image

FileUtility.cs

Base資料夾下的FileUtility.cs封裝了搜尋檔案的方法,寫的不太好,湊活能用

public class FileUtility
    {
        public static List<string> GetAllFiles(string path, string pattern, bool searchChildFolder)
        {
            List<string> names = new List<string>();
            DirectoryInfo di = new DirectoryInfo(path);

            if (!searchChildFolder)
            {
                FileInfo[] fis = di.GetFiles(pattern);
                foreach (FileInfo fi in fis)
                {
                    names.Add(fi.FullName);
                }
            }
            else
            {
                GetFile(path, pattern, names);
            }
            return names;
        }

        public static void GetFile(string path, string pattern, List<string> names)
        {
            DirectoryInfo di = new DirectoryInfo(path);
            string[] patterns=pattern.Split(new char[]{'|',','},StringSplitOptions.RemoveEmptyEntries);
            foreach (string p in patterns)
            {
                FileInfo[] fis = di.GetFiles(p.Trim());
                foreach (FileInfo fi in fis)
                {
                    names.Add(fi.FullName);
                }
            }

            DirectoryInfo[] dis = di.GetDirectories();
            if (dis.Length != 0)
            {
                foreach (DirectoryInfo _di in dis)
                {
                    GetFile(_di.FullName, pattern, names);
                }
            }
        }
    }

Algorithm

Algorithm 這個project Assembly name 是SSTool.Algorithm,存放所有修改檔案的演算法,Base資料夾下的IAlgorithm是所有演算法都要實現的介面

public interface IAlgorithm
    {
        void execute(List<string> files);
    }

DemoAlgorithem.cs是一個簡單的示例演算法,檔案內寫一行資料

public class DemoAlgorithem:IAlgorithm
    {

        public void execute(List<string> files)
        {
            foreach (string path in files)
            {
                using (StreamWriter sw = new StreamWriter(path, false))
                {
                    sw.WriteLine("This is a text.");
                }
            }
        }
    }

 

每新增一個演算法就需要在AlgorithmConf.xml新增一條記錄

<?xml version="1.0" encoding="utf-8" ?>
<algorithms>
    <item key="Demo" algorithm="SSTool.Algorithm.DemoAlgorithem" />
</algorithms>

這是為後面工廠模式反射獲取所有演算法做準備

ArithmeticFactory.cs

這個是演算法的工產類,用於生成演算法物件例項,也就是工廠模式中的工廠類了,IAlgorithm是產品類介面,而DemoAlgorithem是一個具體產品,看看怎麼生產演算法產品吧

我使用了反射的方式獲取演算法例項,程式碼很簡單

public IAlgorithm CreateUpdateAlgorithm(string key)
        {
            Assembly assembly = Assembly.Load("SSTool.Algorithm");
            Dictionary<string, string> map = GetConf("AlgorithmConf.xml");
            IAlgorithm algorithm = (IAlgorithm)assembly.CreateInstance(map[key]);
            return algorithm;
        }

        public Dictionary<string, string> GetConf(string path)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load("AlgorithmConf.xml");
            XmlNodeList nodes = doc.SelectNodes("//item");
            Dictionary<string, string> map = new Dictionary<string, string>();
            foreach (XmlNode node in nodes)
            {
                map.Add(node.Attributes["key"].Value, node.Attributes["algorithm"].Value);
            }
            return map;
        }

 

MainFrame.cs

MainFrame.cs用於生成外觀及響應事件

為類新增一個屬性,用於獲取工廠例項

private ArithmeticFactory factory;
        public ArithmeticFactory Factory
        {
            get
            {
                if (factory != null)
                {
                    return factory;
                }
                else
                {
                    factory = new ArithmeticFactory();
                    return factory;
                }
            }
        }

在Load事件處理程式中使用工廠的GetConf獲取所有配置,把key繫結到介面選擇演算法的Combobox中

protected override void OnLoad(EventArgs e)
        {
            Dictionary<string, string> map = Factory.GetConf("AlgorithmConf.xml");
            this.cmbArithmetic.Items.Clear();
            foreach (KeyValuePair<string, string> pair in map)
            {
                this.cmbArithmetic.Items.Add(pair.Key);
            }
            base.OnLoad(e);
        }

 

按鈕

1.點選 “Select”按鈕的時候調出選擇資料夾視窗

private void btnSelect_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            dialog.Description = "Select Folder Path";
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                this.txtPath.Text = dialog.SelectedPath;
            }
        }

2. 點選 “Search” 按鈕的時候根據資料夾及Search pattern 搜尋檔案列表並顯示在下面列表中

dgvFiles.Columns.Clear();
            List<string> names = FileUtility.GetAllFiles(this.txtPath.Text, this.txtPattern.Text.Trim(), this.ckbRecursive.Checked);
            DataTable dt = new DataTable();
            dt.Columns.Add("File");
            foreach (string name in names)
            {
                DataRow dr = dt.NewRow();
                dr[0] = name;
                dt.Rows.Add(dr);
            }
            dgvFiles.DataSource = dt;
            dgvFiles.Columns[0].Width = dgvFiles.Width;
            for (int i = 0; i < dgvFiles.Rows.Count; i++)
            {
                dgvFiles.Rows[i].Selected = true;
            }

3. 在介面上篩選搜尋結果,選擇演算法,點選“Update”按鈕,做一些輸入驗證工作後更新檔案,更新演算法例項使用工廠獲得

private void btnUpdate_Click(object sender, EventArgs e)
        {
            string error = ValidateInput();
            if (error == null)
            {
                IAlgorithm algorithm 
= Factory.CreateUpdateAlgorithm(this
.cmbArithmetic.SelectedItem.ToString());
                List<string> files = new List<string>();
                for (int i = 0; i < dgvFiles.Rows.Count; i++)
                {
                    if(dgvFiles.Rows[i].Selected ==true)
                    {
                        files.Add(dgvFiles.Rows[i].Cells[0].Value.ToString());
                    }
                }
                algorithm.execute(files);
                this.panDisplay.Enabled = false;
                this.progressUpdate.Visible = true;
                this.progressUpdate.Value = 0;
                this.progressUpdate.Visible = false;
                this.panDisplay.Enabled = true;
                MessageBox.Show("Done!", "Update");
            }
            else
            {
                MessageBox.Show(error, "Error");
            }
        }

最後

這樣一個簡單的檔案批量修改工具就完成了,點選這裡下載原始碼,其實這個工具很簡單,沒任何高明之處,反而本著釋出去寫的小工具甚至有些粗糙,只是想借此說明幾點

1. 設計模式與我們日常程式設計工作並不是遙不可及的,設計模式並不只是架構師的菜,只要認真發覺,我們每天使用的類庫中都包含很多設計模式,有時候我們也在使用了,只是沒發現

2. 設計模式的學習並不是看幾本書、在網上看個大牛寫的一系列教程就可以做到的,而在於我們對自己寫的程式碼精益求精,發現違背設計原則的地方不斷重構,結合理論指導,自然能夠用出設計模式,一旦設計模式是自己慢慢演化去程式碼得來,相信大家就不在會問為什麼要用這個模式、用了有什麼好處、什麼場景下用這個設計模式了。

紙上得來終覺淺,絕知此事要躬行。

相關文章