更好地程式設計
一晃發現有些日子沒有寫點東西了,再加上這一段遇到的事情,就把自己的思考做個提煉吧。事情的起由是這樣的:
其實,對於一個特定的功能,要很快把它寫出來並非難事,但要考慮周全,如封裝性、複用性、耦合性及便捷性等,其中的難度和平衡,就沒那麼容易的,冰凍三尺非一日之寒,一方面需要自己不斷實踐累積,另一方面也需要不斷拓寬知識面。
加上這兩天.NET開源的勁爆訊息影響,這次就且用C#來做說明。其實,我一直覺得C#的語法比Java先進許多,雖說其借鑑於Java,但語法上已超越Java。這次開源真是大好事,如果沒有開源,我還真不敢如此放心大膽地使用它。
在這裡,我會使用一個常見的影片導購車作為例子:比如你想實現個影片導購車,統計各部影片的總票價。我會按先易後難的步驟一一說明,因此,加上完整程式碼,估計篇幅會比較長,稍需耐心。
需要事先說明一點的是:為了簡化說明,我用了靜態模型資料陣列來模擬從資料庫中獲取的資料,實際專案中這些資料往往是從資料庫中獲取到的。
一. 最簡單的實現
一般這個實現在現實專案中並不常見,除非在練習過程中,或對於一個newbie程式設計師而言。
程式程式碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DIApp1
{
//Model類
public class Movie
{
public int MovieID { get; set; }
public string MovieName { get; set; }
public decimal MovieCost { get; set; }
public decimal MoviePrice { set; get; }
}
class Program
{
//模型資料陣列,這裡為了簡化資料庫資料獲取,用靜態陣列替代
private static Movie[] movies = {
new Movie {MovieID = 1, MovieName = "猩猿崛起II", MovieCost = 80000000.00M, MoviePrice = 80.00M},
new Movie {MovieID = 2, MovieName = "生化危機", MovieCost = 5000000.00M, MoviePrice = 50.00M},
new Movie {MovieID = 3, MovieName = "大話西遊", MovieCost = 3000000.00M, MoviePrice = 35.00M},
new Movie {MovieID = 4, MovieName = "葉問II", MovieCost = 58000000.00M, MoviePrice = 80.00M},
new Movie {MovieID = 5, MovieName = "魔戒", MovieCost = 180000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 6, MovieName = "哈利波特", MovieCost = 170000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 7, MovieName = "高考1977", MovieCost = 9000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 8, MovieName = "縱橫天下", MovieCost = 3150000.00M, MoviePrice = 40.00M},
new Movie {MovieID = 9, MovieName = "醉拳", MovieCost = 2000000.00M, MoviePrice = 40.00M}
};
static void Main(string[] args)
{
Console.WriteLine(movies.Sum(p => p.MoviePrice));
Console.ReadKey(true);
}
}
}
這個程式很簡單,所以實現起來也最快了:通過Lambda直接對陣列進行遍歷,很快就求出了導購車的總票價:
505.00
但這個程式問題非常多,基本就是寫死了程式,耦合度高,複用性低,更別談什麼封裝性了。這個程式裡處理邏輯:Movies.Sum(p => p.MoviePrice)
只呼叫了1次,如果在複雜程式裡呼叫了數十、數百次,一旦要改動它,那改動量可想而知了。
那麼,我想,大多數有些經驗的程式設計師應該都不會這麼做,那會怎麼做呢?我覺得70%(?)會選擇第二種方式吧,也就是前面所說我看到的方式。
二. 簡單的工具類封裝方式
這種方式的程式程式碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DIApp2
{
//Model類
public class Movie
{
public int MovieID { get; set; }
public string MovieName { get; set; }
public decimal MovieCost { get; set; }
public decimal MoviePrice { set; get; }
}
//負責Movie計算邏輯的類實現
public class MoviesCalculator
{
public decimal CalculateMovies(IEnumerable<Movie> movies)
{
return movies.Sum(p => p.MoviePrice);
}
}
class Program
{
//模型資料陣列,這裡為了簡化資料庫資料獲取,用靜態陣列替代
private static Movie[] movies = {
new Movie {MovieID = 1, MovieName = "猩猿崛起II", MovieCost = 80000000.00M, MoviePrice = 80.00M},
new Movie {MovieID = 2, MovieName = "生化危機", MovieCost = 5000000.00M, MoviePrice = 50.00M},
new Movie {MovieID = 3, MovieName = "大話西遊", MovieCost = 3000000.00M, MoviePrice = 35.00M},
new Movie {MovieID = 4, MovieName = "葉問II", MovieCost = 58000000.00M, MoviePrice = 80.00M},
new Movie {MovieID = 5, MovieName = "魔戒", MovieCost = 180000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 6, MovieName = "哈利波特", MovieCost = 170000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 7, MovieName = "高考1977", MovieCost = 9000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 8, MovieName = "縱橫天下", MovieCost = 3150000.00M, MoviePrice = 40.00M},
new Movie {MovieID = 9, MovieName = "醉拳", MovieCost = 2000000.00M, MoviePrice = 40.00M}
};
static void Main(string[] args)
{
MoviesCalculator moviesCalculator = new MoviesCalculator();
Console.WriteLine(moviesCalculator.CalculateMovies(movies));
Console.ReadKey(true);
}
}
}
這次,多了一個MoviesCalculator工具類,封裝了業務處理邏輯,其中的CalculateMovies方法傳入Movies,計算後輸出結果。
這種方式的邏輯比較清晰了,至少可以到處複用MoviesCalculator工具類,而且邏輯修改也只需要在CalculateMovies方法完成即可。但是,還是有問題:
下次,我不統計電影了,我統計電視劇了,那
moviesCalculator.CalculateMovies(Movies)
就不是傳Movies了,程式一旦上規模了,那得改多少處吖?!是不是還得生成一個電視劇的處理類?這個電視劇的處理類想過去跟電影的處理類肯定有很多相似之處,那如何複用?
所以,這個方式還是有很大不足。
於是,我們來思考一下第三種方式。
三. 介面、依賴注入
通過介面及依賴注入(DI)等機制的保障,我們可以做得更好。先上程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DIApp
{
//Model類
public class Movie
{
public int MovieID { get; set; }
public string MovieName { get; set; }
public decimal MovieCost { get; set; }
public decimal MoviePrice { set; get; }
}
//模型資料類
public static class Movies
{
//模型資料陣列,這裡為了簡化資料庫資料獲取,用靜態陣列替代,實際可以實現get/set方法
public static Movie[] movies = {
new Movie {MovieID = 1, MovieName = "猩猿崛起II", MovieCost = 80000000.00M, MoviePrice = 80.00M},
new Movie {MovieID = 2, MovieName = "生化危機", MovieCost = 5000000.00M, MoviePrice = 50.00M},
new Movie {MovieID = 3, MovieName = "大話西遊", MovieCost = 3000000.00M, MoviePrice = 35.00M},
new Movie {MovieID = 4, MovieName = "葉問II", MovieCost = 58000000.00M, MoviePrice = 80.00M},
new Movie {MovieID = 5, MovieName = "魔戒", MovieCost = 180000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 6, MovieName = "哈利波特", MovieCost = 170000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 7, MovieName = "高考1977", MovieCost = 9000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 8, MovieName = "縱橫天下", MovieCost = 3150000.00M, MoviePrice = 40.00M},
new Movie {MovieID = 9, MovieName = "醉拳", MovieCost = 2000000.00M, MoviePrice = 40.00M}
};
//public IEnumerable<Movie> movies { get; set; }
}
//負責Movie計算邏輯的介面實現
public interface IMoviesCalculator
{
decimal CalculateMovies(IEnumerable<Movie> movies);
}
//負責Movie計算邏輯的類實現
public class MoviesCalculator : IMoviesCalculator
{
public decimal CalculateMovies(IEnumerable<Movie> movies)
{
return movies.Sum(p => p.MoviePrice);
}
}
//Movie導購車類
public class MoviesCart
{
private IMoviesCalculator moviesCalculator;
public MoviesCart(IMoviesCalculator moviesCalculator)
{
this.moviesCalculator = moviesCalculator;
}
public decimal CalculateMovies()
{
return moviesCalculator.CalculateMovies(Movies.movies);
}
}
class Program
{
static void Main(string[] args)
{
IMoviesCalculator moviesCalculator = new MoviesCalculator();
MoviesCart moviesCart = new MoviesCart(moviesCalculator);
Console.WriteLine(moviesCart.CalculateMovies());
Console.ReadKey(true);
}
}
}
這裡我們使用了構造器注入的方式(DI還有設值注入、介面注入等方式)。
public MoviesCart(IMoviesCalculator moviesCalculator)
{
this.moviesCalculator = moviesCalculator;
}
可以看到,MoviesCart接收的引數是IMoviesCalculator,那麼,任何實現該介面的類,不管是電影、電視劇,或者你還想細分的動畫片、槍戰片、魔幻片,都可以用MoviesCart來處理,這樣,導購車就可以複用了。你可以實現各種XxxxCalculator及其包含的CalculateMovies方法,在導購車裡填入對應的資料,結果就出來了,多好啊!
結果已經很不錯了,但是請等等!還有個問題!那就是: Main裡面的這兩行程式碼:
IMoviesCalculator moviesCalculator = new MoviesCalculator();
MoviesCart moviesCart = new MoviesCart(moviesCalculator);
我們還是得在程式的每個呼叫處例項化具體的XxxxCalculator類!所以,有可能你還得改動多處的程式碼。
那還有辦法繼續改進嗎?
四. 使用依賴注入容器
使用DI容器是非常高效的,.NET、Java都有很多很好的DI容器,我在這裡使用Ninject(Ninject · GitHub)。
程式程式碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject;
namespace DIAppPro
{
//Model類
public class Movie
{
public int MovieID { get; set; }
public string MovieName { get; set; }
public decimal MovieCost { get; set; }
public decimal MoviePrice { set; get; }
}
//模型資料類
public static class Movies
{
//模型資料陣列,這裡為了簡化資料庫資料獲取,用靜態陣列替代,實際可以實現get/set方法
public static Movie[] movies = {
new Movie {MovieID = 1, MovieName = "猩猿崛起II", MovieCost = 80000000.00M, MoviePrice = 80.00M},
new Movie {MovieID = 2, MovieName = "生化危機", MovieCost = 5000000.00M, MoviePrice = 50.00M},
new Movie {MovieID = 3, MovieName = "大話西遊", MovieCost = 3000000.00M, MoviePrice = 35.00M},
new Movie {MovieID = 4, MovieName = "葉問II", MovieCost = 58000000.00M, MoviePrice = 80.00M},
new Movie {MovieID = 5, MovieName = "魔戒", MovieCost = 180000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 6, MovieName = "哈利波特", MovieCost = 170000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 7, MovieName = "高考1977", MovieCost = 9000000.00M, MoviePrice = 60.00M},
new Movie {MovieID = 8, MovieName = "縱橫天下", MovieCost = 3150000.00M, MoviePrice = 40.00M},
new Movie {MovieID = 9, MovieName = "醉拳", MovieCost = 2000000.00M, MoviePrice = 40.00M}
};
//public IEnumerable<Movie> movies { get; set; }
}
//負責Movie計算邏輯的介面實現
public interface IMoviesCalculator
{
decimal CalculateMovies(IEnumerable<Movie> movies);
}
//負責Movie計算邏輯的類實現
public class MoviesCalculator : IMoviesCalculator
{
public decimal CalculateMovies(IEnumerable<Movie> movies)
{
return movies.Sum(p => p.MoviePrice);
}
}
//Movie導購車類
public class MoviesCart
{
private IMoviesCalculator moviesCalculator;
public MoviesCart(IMoviesCalculator moviesCalculator)
{
this.moviesCalculator = moviesCalculator;
}
public decimal CalculateMovies()
{
return moviesCalculator.CalculateMovies(Movies.movies);
}
}
//Ninject設定靜態類
public static class NinjectDependencyResolver {
public static IMoviesCalculator calculator;
static NinjectDependencyResolver()
{
IKernel nKernel = new StandardKernel();
//介面與具體類例項的繫結
nKernel.Bind<IMoviesCalculator>().To<MoviesCalculator>();
calculator = nKernel.Get<IMoviesCalculator>();
}
}
class Program
{
static void Main(string[] args)
{
MoviesCart moviesCart = new MoviesCart(NinjectDependencyResolver.calculator);
Console.WriteLine(moviesCart.CalculateMovies());
Console.ReadKey(true);
}
}
}
在這裡,最主要的是引入了DI解析類NinjectDependencyResolver,
//介面與具體類例項的繫結
nKernel.Bind<IMoviesCalculator>().To<MoviesCalculator>();
calculator = nKernel.Get<IMoviesCalculator>();
通過Ninject,我們把介面和具體實現類例項繫結在一起,這樣,你要改變類例項時,只要在這一處地方改動就可以了。在Main裡面,你再也不用糾結還有侵入式程式碼的存在了!結果依舊輸出:
505.00
五. 想說的
這裡不是想說程式碼的實現,如果說下去,估計要寫很多。同時,列出這些程式碼也只是為了一步步地說明問題。我最想說的,是程式設計的思路,人人都能寫程式碼,但程式碼好壞是現實存在的。要想馴服爛程式碼,培養程式設計大局觀非常重要,這是所謂工程師和程式設計師的區別,不管是簡單的程式碼還是複雜的程式碼,都應該具備大局觀。
不要為了方法、設計模式、程式設計正規化而刻意去做,也就是不要過度設計,針對具體情況採取“權衡”方案才更貼切實際,殺雞不用牛刀,但該出手時也要毫不猶豫;
但從另一個角度來講,掌握好必要的技能,在需要的時候用得上並懂得用,這也非常重要;
團隊開發,講究的是配合,個人技能固然重要,但現實中始終存在良莠不齊的情況。所以,團隊開發過程中的工程化思維更加重要,要通過培訓、規約、計劃管控、版本控制等等手段,來協調開發。比如,制定開發模式和技術規約,把好的方式固化下來。組織團隊成員統一培訓並掌握良好開發技能,比靠個人摸索的效率要高多了,但是,可惜,這樣的好團隊太少了......
六. 其他
這兩天關於.NET開源及跨平臺的思路真是利好訊息,相關連結如下:
Visual Studio Community 2013下載地址:Visual Studio Community 2013 Downloads
Visual Studio 2015 預覽版下載地址:Visual Studio 2015 Downloads
相關文章
- 12條自問讓你更好地程式設計程式設計
- 【譯】更好地設計 React 元件React元件
- 如何更好地設計手機軟體介面
- 國外技術大牛通過12條測試讓你更好地程式設計程式設計
- 國外技術大牛通過12條測試讓你更好地程式設計(下)程式設計
- 國外技術大牛通過12條測試讓你更好地程式設計(上)程式設計
- 程式設計師如何寫出更好的程式碼程式設計師
- 學程式設計,Python和Java哪個更好?程式設計PythonJava
- 讓我們成為更好的程式設計師程式設計師
- 五個方法成為更好的程式設計師程式設計師
- 設計更好的資料表格設計
- 如何更好地建立物件物件
- 如何成為更好的Java程式設計師?- javarevisitedJava程式設計師
- 成為更好程式設計師的8種途徑程式設計師
- 像鳥一樣思考更好的並行程式設計並行行程程式設計
- GitHub---程式設計師的聖地Github程式設計師
- 成為更好的程式設計師必須學習的 4 種程式設計結構程式設計師
- 更好地使用 ViewControllerViewController
- 如何更好地學習機器學習?機器學習
- 如何更好的設計 RESTful APIRESTAPI
- 如何更好的設計RESTful APIRESTAPI
- 如何利用Google成為一個更好的程式設計師Go程式設計師
- 三個方法讓你成為更好的程式設計師程式設計師
- Mac:更好地工作 – 系統配置Mac
- 我們需要更多的程式設計師,而不是更好的工具程式設計師
- 區塊鏈如何更好地改進雲端計算解決方案區塊鏈
- Node 中如何更好地打日誌
- LINUX系統程式設計 LINUX地區(locale)設定Linux程式設計
- JavaScript是如何工作的:事件迴圈和非同步程式設計的崛起 + 5種使用 async/await 更好地編碼方式!JavaScript事件非同步程式設計AI
- 0基礎小白學程式設計,Java和Python哪個更好?程式設計JavaPython
- 83天!風變程式設計讓我成為了更好的自己程式設計
- 2014,成為更好程式設計師的7個方法程式設計師
- 一句話讓你成為更好的PHP程式設計師PHP程式設計師
- Nielsen:如何設計出更好的網站網站
- 程式設計學習筆記之訊息地圖程式設計筆記地圖
- 程式設計師跳槽時,如何高效地準備面試?程式設計師面試
- 程式設計師的生活:學會放鬆地工作程式設計師
- 遊戲UX設計:地圖設計的考量遊戲UX地圖