2012-04-23 43 views
9

Tôi có một số mã có nhiều trùng lặp khủng khiếp. Vấn đề xuất phát từ thực tế là tôi đang xử lý các loại lồng nhau IDisposable. Hôm nay tôi có cái gì đó trông giống như:Làm thế nào một mã refactor có thể tham gia vào các ứng dụng lồng nhau?

public void UpdateFromXml(Guid innerId, XDocument someXml) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    using (var b = a.GetB(_id)) 
    using (var c = b.GetC(innerId)) 
    { 
     var cWrapper = new SomeWrapper(c); 
     cWrapper.Update(someXml); 
    } 
} 

public bool GetSomeValueById(Guid innerId) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    using (var b = a.GetB(_id)) 
    using (var c = b.GetC(innerId)) 
    { 
     return c.GetSomeValue(); 
    } 
} 

Toàn bộ lồng nhau using khối là như nhau cho mỗi một trong các phương pháp (hai được hiển thị, nhưng có khoảng mười trong số họ). Điều duy nhất khác biệt là những gì xảy ra khi bạn đi đến cấp độ bên trong của các khối using.

Một cách tôi đã suy nghĩ sẽ làm điều gì đó dọc theo dòng:

public void UpdateFromXml(Guid innerId, XDocument someXml) 
{ 
    ActOnC(innerId, c => 
    { 
     var cWrapper = new SomeWrapper(c); 
     cWrapper.Update(someXml); 
    }); 
} 

public bool GetSomeValueById(Guid innerId) 
{ 
    var result = null; 

    ActOnC(innerId, c => { result = c.GetSomeValue(); }); 

    return result; 
} 

private void ActOnC(Guid innerId, Action<TheCType> action) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    using (var b = a.GetB(_id)) 
    using (var c = b.GetC(innerId)) 
    { 
     action(c); 
    }   
} 

này hoạt động, nó chỉ là loại clunky để phân tích (như là một con người). Có ai có bất kỳ đề xuất nào khác về cách người ta có thể giảm sự sao chép mã xung quanh các khối using lồng nhau như thế này không? Nếu chúng không phải là IDisposable thì có thể chỉ cần tạo phương thức trả về kết quả là b.GetC(innerId) ... nhưng đó không phải là trường hợp ở đây.

+3

+1 Tôi không thấy bất cứ điều gì phiền phức trong giải pháp của bạn. Đó là loại không chính thống được nhiều chức năng hơn thủ tục, nhưng tôi sẽ đếm nó như là một chuyên nghiệp, không phải là con – mfeingold

+1

Tôi nghĩ rằng thực hiện của bạn có vẻ tốt nhưng có lẽ bạn sẽ thích một số lựa chọn thay thế được cung cấp dưới đây. Nếu bạn thấy mình phải chuỗi rất nhiều lần có thể tháo rời, bạn có thể muốn xem xét việc thiết kế lại những thứ để bạn không kết thúc trong tình huống này. – Thomas

Trả lời

1

Tôi thích câu trả lời được cung cấp bởi BFree khi bắt đầu, nhưng tôi sẽ thực hiện một vài sửa đổi.

//Give it a better name; this isn't designed to be a general purpose class 
public class MyCompositeDisposable : IDisposable 
{ 
    public MyCompositeDisposable (string uri, int id, int innerid) 
    { 
     A = SomeFactory.GetA(uri); 
     B = A.GetB(id); 
     C = B.GetC(innerId); 
    } 

    //You can make A & B private if appropriate; 
    //not sure if all three or just C should be exposed publicly. 
    //Class names are made up; you'll need to fix. 
    //They should also probably be given more meaningful names. 
    public ClassA A{get;private set;} 
    public ClassB B{get;private set;} 
    public ClassC C{get;private set;} 

    public void Dispose() 
    { 
     A.Dispose(); 
     B.Dispose(); 
     C.Dispose(); 
    } 
} 

Sau khi thực hiện mà bạn có thể làm điều gì đó như:

public bool GetSomeValueById(Guid innerId) 
{ 
    using(MyCompositeDisposable d = new MyCompositeDisposable(_uri, _id, innerId)) 
    { 
     return d.C.GetSomeValue(); 
    } 
} 

Lưu ý rằng MyCompositeDisposable khả năng sẽ cần phải có thử/cuối cùng khối trong các nhà xây dựng và Vứt bỏ các phương pháp để sai sót trong việc tạo ra/phá hủy đúng cách đảm bảo không có gì kết thúc lên không được xử lý.

+0

Ý tưởng đóng gói tất cả trong một lớp như thế này là hoàn hảo cho nhu cầu của tôi và cung cấp sự cân bằng giữa mã trùng lặp và tính linh hoạt cho tất cả các trường hợp của tôi, và thậm chí nó còn giúp phân tách các mối quan tâm một chút. Điều này có khá nhiều câu trả lời tốt nhất. Cảm ơn. – ckittel

+0

Điều này có lỗi tương tự như câu trả lời của BFree - một ngoại lệ trong khi xây dựng C sẽ để lại A và B không được xử lý. –

+1

@DavidB Tôi đã có một lưu ý ở phần cuối của câu trả lời rằng kiểm tra lỗi như vậy là cần thiết, nhưng nó không được bao gồm trong câu trả lời ở đây. Nếu nó cần thiết trong trường hợp của OP, anh ta biết anh ta cần phải thêm nó. – Servy

0

Nếu Dispoable loại bỏ chính xác tất cả các thành viên dùng một lần, bạn chỉ cần một câu lệnh sử dụng.

Ví dụ này:

public bool GetSomeValueById(Guid innerId) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    using (var b = a.GetB(_id)) 
    using (var c = b.GetC(innerId)) 
    { 
     return c.GetSomeValue(); 
    } 
} 

có thể trở thành này nếu có các thành viên của loại hình của b và c, và thanh lý khoản của b và c trong phương pháp xử lý của nó:

public bool GetSomeValueById(Guid innerId) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    { 
     return a.GetSomeValue(); 
    } 
} 

class A : IDisposable 
{ 
    private a; 
    private b; 

    public A (B b, C c) 
    { 
    this.b = b; this.c = c; 
    } 

    public void Dispose() 
    { 
    Dispose(true); 
    } 

    protected void Dispose(bool disposing) 
    { 
    if (disposing) 
    { 
     b.Dispose(); 
     c.Dispose(); 
    } 
    } 
} 

Bạn sẽ phải sửa đổi nhà máy của bạn để tiêm b và c vào một, tuy nhiên.

+2

Bạn nên cẩn thận khi để vật thể vứt bỏ đồ vật do lớp khác đưa ra. Điều gì sẽ xảy ra nếu nhiều hơn một cá thể đang dựa vào đối tượng đó? Việc xử lý nói chung là trách nhiệm của lớp sở hữu, và trong trường hợp này 'A' không sở hữu' b' và 'c'. – Thomas

+0

@Thomas điểm tuyệt vời. Thông thường, bạn cũng sẽ có tham số boolean ctor cho biết A sở hữu b và c. – jrummell

1

Trong khuôn khổ Rx có một lớp được gọi là CompositeDisposablehttp://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable%28v=vs.103%29.aspx

không nên quá khắt khe cuộn của riêng (mặc dù rất tước xuống phiên bản) của bạn:

public class CompositeDisposable : IDisposable 
{ 
    private IDisposable[] _disposables; 

    public CompositeDisposable(params IDisposable[] disposables) 
    { 
     _disposables = disposables; 
    } 

    public void Dispose() 
    { 
     if(_disposables == null) 
     { 
      return; 
     } 

     foreach(var disposable in _disposables) 
     { 
      disposable.Dispose(); 
     } 
    } 
} 

Sau đó, điều này có vẻ một chút sạch hơn:

public void UpdateFromXml(Guid innerId, XDocument someXml) 
{ 
    var a = SomeFactory.GetA(_uri); 
    var b = a.GetB(_id); 
    var c = b.GetC(innerId); 
    using(new CompositeDisposable(a,b,c)) 
    { 
     var cWrapper = new SomeWrapper(c); 
     cWrapper.Update(someXml); 
    } 
} 
+2

nếu ngoại lệ xảy ra trong b.GetC - Tôi không nghĩ rằng a và b được xử lý đúng khi điều đó xảy ra. –

1

Bạn luôn có thể tạo ngữ cảnh lớn hơn để quản lý các đối tượng cần được tạo/xử lý. Sau đó viết một phương thức để tạo ngữ cảnh lớn hơn đó ...

public class DisposeChain<T> : IDisposable where T : IDisposable 
{ 
    public T Item { get; private set; } 
    private IDisposable _innerChain; 

    public DisposeChain(T theItem) 
    { 
     this.Item = theItem; 
     _innerChain = null; 
    } 

    public DisposeChain(T theItem, IDisposable inner) 
    { 
     this.Item = theItem; 
     _innerChain = inner; 
    } 

    public DisposeChain<U> Next<U>(Func<T, U> getNext) where U : IDisposable 
    { 
     try 
     { 
      U nextItem = getNext(this.Item); 
      DisposeChain<U> result = new DisposeChain<U>(nextItem, this); 
      return result; 
     } 
     catch //an exception occurred - abort construction and dispose everything! 
     { 
      this.Dispose() 
      throw; 
     } 
    } 

    public void Dispose() 
    { 
     Item.Dispose(); 
     if (_innerChain != null) 
     { 
      _innerChain.Dispose(); 
     } 
    } 
} 

Sau đó sử dụng nó:

public DisposeChain<DataContext> GetCDisposeChain() 
    { 
     var a = new DisposeChain<XmlWriter>(XmlWriter.Create((Stream)null)); 
     var b = a.Next(aItem => new SqlConnection()); 
     var c = b.Next(bItem => new DataContext("")); 

     return c; 
    } 

    public void Test() 
    { 
     using (var cDisposer = GetCDisposeChain()) 
     { 
      var c = cDisposer.Item; 
      //do stuff with c; 
     } 
    } 
Các vấn đề liên quan