.NET应用架构设计—用户端的防腐层作用及设计

阅读目录:

  • 1.背景介绍
  • 2.SOA架构下的显示端架构腐化
  • 3.有效使用防腐层来隔离碎片服务导致显示端逻辑腐烂
  • 4.剥离服务调用的技术组件让其依赖接口
  • 5.将服务的DTO与显示端的ViewModel之间的转换放入防腐层
    • 5.1.转换逻辑过程化,直接写在防腐层的方法中
    • 5.2.转换逻辑对象化,建立起封装、重用结构,防止进一步腐化
  • 6.防腐层的两种依赖倒置设计方法
    • 6.1.事件驱动(防腐层监听显示逻辑事件)
    • 6.2.依赖注入接口
  • 7.总结

1.背景介绍

随着现在的企业应用架构都在向着SOA方向转变,目的就是将一个庞大的业务系统按照业务进行划分,不管从公司的管理上、产品的开发上,这一系列流程来看,都是正确的。SOA确实带来了解决现在大型企业级应用系统快速膨胀的解决办法。

但是本文要说的是,我们都将目光转向到了后端,也就是服务端,而将精力和时间都重点投在了后端服务的架构设计上,渐渐的忽视了显示端的架构设计。然而显示端的逻辑也越来越复杂,显示端轻薄的架构其实已经浮现出难以应付后端服务接口快速膨胀的危险,服务接口都是按照指数级增加,基本上每一个新的业务需求都是提供新的接口,这没有问题。按照服务的设计原则,服务接口就应该有着明确的作用,而不是按照代码的思维来考虑接口的设计。

但是由此带来的问题就是组合这些接口的显示端的结构是否和这种变化是一致的,是否做好了这种变化带来显示端逻辑复杂的准备。

根据我自己的亲身体会,我发现显示端的架构设计不被重视,这里的重视不是老板是否重视,而是我们开发人员没有重视,当然这里排除时间问题。我观察过很多用户接口项目架构,结构及其简单,没有封装、没有重用,看不到任何的设计原则。这样就会导致这些代码很难随着业务的快速推动由服务接口带来的冲击,这里还有一个最大的问题就是,作为程序员的我们是否有快速重构的意识,我很喜欢这条程序员职业素质。它可以让我们敏捷的、快速的跟上由业务的发展带来的项目结构的变化。

迭代重构对项目有着微妙的作用,重构不能够过早也不能够过迟,要刚好在需要的时候重构。对于重构我的经验就是,当你面对新功能写起来比较蹩脚的时候时,这是一个重构信号,此时应该是最优的重构时间。重构不是专门的去准备时间,而是穿插在你写代码的过程中,它是你编码的一部分。所以我觉得TDD被人接受的理由也在于此。

2.SOA架构下的显示端架构腐化

显示端的架构腐化我个人觉得有两个问题导致,第一个,原本显示端的结构在传统系统架构中可以工作的很好,但是现在的整体架构变了,所以需要及时作出调整。第二,显示端的架构未能及时的重构,未能将显示端结构进行进一步分离,将显示逻辑独立可测试。

这样随着SOA接口的不断增加,显示端直接将调用服务的方法嵌入到显示逻辑中,如,ASP.NET Mvc、ASP.NET Webapi的控制器中,包括两个层面之间的DTO转换。

按照DDD的上下文设计方法,在用户显示端也是可以有选择的创建面向显示的领域模型,此模型主要处理领域在即将到达服务端之后的前期处理。毕竟一个领域实体有着多个方面的职责,如果能在显示端建立起轻量级的领域模型,对显示逻辑的重构将大有好处,当然前提是你有着复杂的领域逻辑。(我之前的上一家公司(美国知名的电子商务平台),他们的显示端有着复杂的领域逻辑,就光一个显示端就复杂的让人吃惊,如果能在此基础上引入领域模型显示端上下文,将对复杂的逻辑处理很有好好处,当然这只是我未经验证的猜测而已,仅供参考。)

对显示端领域模型处理有兴趣的可以参考本人写的有关这方面的两篇文章:

.NET应用架构设计—面向查询的领域驱动设计实践(调整传统三层架构,外加维护型的业务开关)

.NET应用架构设计—面向查询服务的参数化查询设计(分解业务点,单独配置各自的数据查询契约)

原本干净的显示逻辑多了很多无关的服务调用细节,还有很多转换逻辑,判断逻辑,而这些东西原本不属于这个地方,让他们放在合适的地方对显示逻辑的重构、重用很有帮助。

如果不将其移出显示逻辑中,那么随着服务接口的不断增加和扩展,将直接导致你修改显示逻辑代码,如果你的显示逻辑代码是MVC、Webapi共用的逻辑,那么情况就更加复杂了,最后显示逻辑里面将被ViewModel与Service Dto之间的转换占领,你很难找到有价值的逻辑了。

3.有效使用防腐层来隔离碎片服务导致显示端逻辑腐烂

解决这些问题的方法就是引入防腐层,尽管防腐层的初衷是为了解决系统集成时的领域模型之间的转换,但是我觉得现在的系统架构和集成有着很多相似之处,我们可以适当的借鉴这些好的设计方法来解决相似的问题。

引入防腐层之后,将原本不该出现在显示逻辑中的代码全部搬到防腐层中来,在防腐层中建立起OO机制,让这些OO对象能够和显示逻辑一起搭配使用。

图1:

将用户层分层三个子层,UiLayer,Show Logic Layer,Anticorrosive Layer,最后一个是服务的接口组,所有的服务接口调用均需要从防腐层走。

我们需要将Show Logic Layer中的服务调用,类型转换代码迁移到Anticorrsoive Layer中,在这里可以对象化转换逻辑也可以不对象化,具体可以看下项目是否需要。如果业务确实比较复杂的时候,那么我们为了封装、重用就需要进行对象化。

4.剥离服务调用的技术组件让其依赖接口

首先要做的就是将逻辑代码中的服务对象重构成面向接口的,然后让其动态的依赖注入到逻辑类型中。在ASP.NETWEBAPI中,我们基本上将显示逻辑都写在这里面,我也将使用此方式来演示本章例子,但是如果你的MVC项目和WEBAPI项目共用显示逻辑就需要将其提出来形成独立的项目(Show Logic Layer)。

 1 using OrderManager.Port.Models;
 2 using System.Collections.Generic;
 3 using System.Web.Http;
 4
 5 namespace OrderManager.Port.Controllers
 6 {
 7     public class OrderController : ApiController
 8     {
 9         [HttpGet]
10         public OrderViewModel GetOrderById(long oId)
11         {
12             OrderService.Contract.OrderServiceClient client = new OrderService.Contract.OrderServiceClient();
13             var order = client.GetOrderByOid(oId);
14
15             if (order == null) return null;
16
17             return AutoMapper.Mapper.DynamicMap<OrderViewModel>(order);
18         }
19     }
20 } 

这是一段很简单的调用Order服务的代码,首先需要实例化一个服务契约中包含的客户端代理,然后通过代理调用远程服务方法GetOrderByOid(long oId)。执行一个简单的判断,最后输出OrderViewModel。

如果所有的逻辑都这么简单我想就不需要什么防腐层了,像这种类型的显示代码是极其简单的,我这里的目的不是为了显示多么的复杂的代码如何写,而是将服务调用调用的代码重构层接口,然后注入进OrderController实例中。目的就是为了能够在后续的迭代重构中对该控制器进行单元测试,这可能有点麻烦,但是为了长久的利益还是需要的。

 1 using OrderManager.Port.Component;
 2 using OrderManager.Port.Models;
 3 using System.Collections.Generic;
 4 using System.Web.Http;
 5
 6 namespace OrderManager.Port.Controllers
 7 {
 8     public class OrderController : ApiController
 9     {
10         private readonly IOrderServiceClient orderServiceClient;
11         public OrderController(IOrderServiceClient orderServiceClient)
12         {
13             this.orderServiceClient = orderServiceClient;
14         }
15
16         [HttpGet]
17         public OrderViewModel GetOrderById(long oId)
18         {
19             var order = orderServiceClient.GetOrderByOid(oId);
20
21             if (order == null) return null;
22
23             return AutoMapper.Mapper.DynamicMap<OrderViewModel>(order);
24         }
25     }
26 } 

为了能在运行时动态的注入到控制器中,你需要做一些基础工作,扩展MVC控制器的初始化代码。这样我们就可以对OrderController进行完整的单元测试。

刚才说了,如果显示逻辑都是这样的及其简单,那么一切都没有问题了,真实的显示逻辑非常的复杂而且多变,并不是所有的类型转换都能使用Automapper这一类动态映射工具解决,有些类型之间的转换还有逻辑在里面。GetOrderById(long oId)方法是为了演示此处的重构服务调用组件用的。

大部分情况下我们是需要组合多个服务调用的,将其多个结果组合起来返回给前端的,这里的OrderViewModel对象里面的Items属性类型OrderItem类型中包含了一个Product类型属性,在正常情况下我们只需要获取订单的条目就行了,但是有些时候确实需要将条目中具体的产品信息也要返回给前台进行部分信息的展现。

 1 using System.Collections.Generic;
 2
 3 namespace OrderManager.Port.Models
 4 {
 5     public class OrderViewModel
 6     {
 7         public long OId { get; set; }
 8
 9         public string OName { get; set; }
10
11         public string Address { get; set; }
12
13         public List<OrderItem> Items { get; set; }
14     }
15 } 

在OrderViewModel中的Items属性是一个List<OrderItem>集合,我们再看OrderItem属性。

 1 using System.Collections.Generic;
 2
 3 namespace OrderManager.Port.Models
 4 {
 5     public class OrderItem
 6     {
 7         public long OitemId { get; set; }
 8
 9         public long Pid { get; set; }
10
11         public float Price { get; set; }
12
13         public int Number { get; set; }
14
15         public Product Product { get; set; }
16     }
17 } 

它里面包含了一个Product实例,有些时候需要将该属性赋上值。

 1 namespace OrderManager.Port.Models
 2 {
 3     public class Product
 4     {
 5         public long Pid { get; set; }
 6
 7         public string PName { get; set; }
 8
 9         public long PGroup { get; set; }
10
11         public string Production { get; set; }
12     }
13 } 

产品类型中的一些信息主要是用来作为订单条目展现时能够更加的人性化一点,你只给一个产品ID,不能够让用户知道是哪个具体的商品。

我们接着看一个随着业务变化带来的代码急速膨胀的例子,该例子中我们需要根据OrderItem中的Pid获取Product完整信息。

 1 using OrderManager.Port.Component;
 2 using OrderManager.Port.Models;
 3 using System.Collections.Generic;
 4 using System.Web.Http;
 5 using System.Linq;
 6
 7 namespace OrderManager.Port.Controllers
 8 {
 9     public class OrderController : ApiController
10     {
11         private readonly IOrderServiceClient orderServiceClient;
12
13         private readonly IProductServiceClient productServiceClient;
14         public OrderController(IOrderServiceClient orderServiceClient, IProductServiceClient productServiceClient)
15         {
16             this.orderServiceClient = orderServiceClient;
17             this.productServiceClient = productServiceClient;
18         }
19
20         [HttpGet]
21         public OrderViewModel GetOrderById(long oId)
22         {
23             var order = orderServiceClient.GetOrderByOid(oId);
24
25             if (order == null && order.Items != null && order.Items.Count > 0) return null;
26
27             var result = new OrderViewModel()
28             {
29                 OId = order.OId,
30                 Address = order.Address,
31                 OName = order.OName,
32                 Items = new System.Collections.Generic.List<OrderItem>()
33             };
34
35             if (order.Items.Count == 1)
36             {
37                 var product = productServiceClient.GetProductByPid(order.Items[0].Pid);//调用单个获取商品接口
38                 if (product != null)
39                 {
40                     result.Items.Add(ConvertOrderItem(order.Items[0], product));
41                 }
42             }
43             else
44             {
45                 List<long> pids = (from item in order.Items select item.Pid).ToList();
46
47                 var products = productServiceClient.GetProductsByIds(pids);//调用批量获取商品接口
48                 if (products != null)
49                 {
50                     result.Items = ConvertOrderItems(products, order.Items);//批量转换OrderItem类型
51                 }
52
53             }
54
55             return result;
56         }
57
58         private static OrderItem ConvertOrderItem(OrderService.OrderItem orderItem, ProductService.Contract.Product product)
59         {
60             if (product == null) return null;
61
62             return new OrderItem()
63             {
64                 Number = orderItem.Number,
65                 OitemId = orderItem.OitemId,
66                 Pid = orderItem.Pid,
67                 Price = orderItem.Price,
68
69                 Product = new Product()
70                 {
71                     Pid = product.Pid,
72                     PName = product.PName,
73                     PGroup = product.PGroup,
74                     Production = product.Production
75                 }
76             };
77         }
78
79         private static List<OrderItem> ConvertOrderItems(List<ProductService.Contract.Product> products, List<OrderService.OrderItem> orderItems)
80         {
81             var result = new List<OrderItem>();
82
83             orderItems.ForEach(item =>
84             {
85                 var orderItem = ConvertOrderItem(item, products.Where(p => p.Pid == item.Pid).FirstOrDefault());
86                 if (orderItem != null)
87                     result.Add(orderItem);
88             });
89
90             return result;
91         }
92     }
93 } 

我的第一感觉就是,显示逻辑已经基本上都是类型转换代码,而且这里我没有添加任何一个有关显示的逻辑,在这样的情况下都让代码急速膨胀了,可想而知,如果再在这些代码中加入显示逻辑,我们基本上很难在后期维护这些显示逻辑,而这些显示逻辑才是这个类的真正职责。

由此带来的问题就是重要的逻辑淹没在这些转换代码中,所以我们急需一个能够容纳这些转换代码的位置,也就是防腐层,在防腐层中我们专门来处理这些转换逻辑,当然我这里的例子是比较简单的,只包含了查询,真正的防腐层是很复杂的,它里面要处理的东西不亚于其他层面的逻辑处理。我们这里仅仅是在转换一些DTO对象而不是复杂的DomainModel对象。

5.将服务的DTO与显示端的ViewModel之间的转换放入防腐层

我们需要一个防腐层来处理这些转换代码,包括对后端服务的调用逻辑,将这部分代码移入防腐对象中之后会对我们后面重构很有帮助。

 1 namespace OrderManager.Anticorrsive
 2 {
 3     using OrderManager.Port.Component;
 4     using OrderManager.Port.Models;
 5     using System.Collections.Generic;
 6     using System.Linq;
 7
 8     /// <summary>
 9     /// OrderViewModel 防腐对象
10     /// </summary>
11     public class OrderAnticorrsive : AnticorrsiveBase<OrderViewModel>, IOrderAnticorrsive
12     {
13         private readonly IOrderServiceClient orderServiceClient;
14
15         private readonly IProductServiceClient productServiceClient;
16
17         public OrderAnticorrsive(IOrderServiceClient orderServiceClient, IProductServiceClient productServiceClient)
18         {
19             this.orderServiceClient = orderServiceClient;
20             this.productServiceClient = productServiceClient;
21         }
22
23         public OrderViewModel GetOrderViewModel(long oId)
24         {
25             var order = orderServiceClient.GetOrderByOid(oId);
26
27             if (order == null && order.Items != null && order.Items.Count > 0) return null;
28
29             var result = new OrderViewModel()
30             {
31                 OId = order.OId,
32                 Address = order.Address,
33                 OName = order.OName,
34                 Items = new System.Collections.Generic.List<OrderItem>()
35             };
36
37             if (order.Items.Count == 1)
38             {
39                 var product = productServiceClient.GetProductByPid(order.Items[0].Pid);//调用单个获取商品接口
40                 if (product != null)
41                 {
42                     result.Items.Add(ConvertOrderItem(order.Items[0], product));
43                 }
44             }
45             else
46             {
47                 List<long> pids = (from item in order.Items select item.Pid).ToList();
48
49                 var products = productServiceClient.GetProductsByIds(pids);//调用批量获取商品接口
50                 if (products != null)
51                 {
52                     result.Items = ConvertOrderItems(products, order.Items);//批量转换OrderItem类型
53                 }
54
55             }
56
57             return result;
58         }
59
60         private static OrderItem ConvertOrderItem(OrderService.OrderItem orderItem, ProductService.Contract.Product product)
61         {
62             if (product == null) return null;
63
64             return new OrderItem()
65             {
66                 Number = orderItem.Number,
67                 OitemId = orderItem.OitemId,
68                 Pid = orderItem.Pid,
69                 Price = orderItem.Price,
70
71                 Product = new Product()
72                 {
73                     Pid = product.Pid,
74                     PName = product.PName,
75                     PGroup = product.PGroup,
76                     Production = product.Production
77                 }
78             };
79         }
80
81         private static List<OrderItem> ConvertOrderItems(List<ProductService.Contract.Product> products, List<OrderService.OrderItem> orderItems)
82         {
83             var result = new List<OrderItem>();
84
85             orderItems.ForEach(item =>
86             {
87                 var orderItem = ConvertOrderItem(item, products.Where(p => p.Pid == item.Pid).FirstOrDefault());
88                 if (orderItem != null)
89                     result.Add(orderItem);
90             });
91
92             return result;
93         }
94     }
95 }

如果你觉得有必要可以将IOrderServiceClient、IProductServiceClient 两个接口放入AnticorrsiveBase<OrderViewModel>基类中。

5.1.转换逻辑过程化,直接写在防腐层的方法中

对于防腐层的设计,其实如果你的转换代码不多,业务也比较简单时,我建议直接写成过程式的代码比较简单点。将一些可以重用的代码直接使用静态的扩展方法来使用也是比较简单方便的,最大问题就是不利于后期的持续重构,我们无法预知未来的业务变化,但是我们可以使用重构来解决。

5.2.转换逻辑对象化,建立起封装、重用结构,防止进一步腐化

相对应的,可以将转换代码进行对象化,形成防腐对象,每一个对象专门用来处理某一个业务点的数据获取和转换逻辑,如果你有数据发送逻辑那么将在防腐对象中大大获益,对象化后就可以直接订阅相关控制器的依赖注入事件,如果你是过程式的代码想完成动态的转换、发送、获取会比较不方便。

6.防腐层的两种依赖倒置设计方法

我们接着看一下如何让防腐对象无干扰的进行自动化的服务调用和发送,我们希望防腐对象完全透明的在执行着防腐的职责,并不希望它会给我们实现上带来多大的开销。

6.1.事件驱动(防腐层监听显示逻辑事件)

我们可以使用事件来实现观察者模式,让防腐层对象监听某个事件,当事件触发时,自动的处理某个动作,而不是要显示的手动调用。

1 namespace OrderManager.Anticorrsive
2 {
3     public interface IOrderAnticorrsive
4     {
5         void SetController(OrderController orderController);
6
7         OrderViewModel GetOrderViewModel(long oId);
8     }
9 }

Order防腐对象接口,里面包含了一个void SetController(OrderController orderController); 重要方法,该方法是用来让防腐对象自动注册事件用的。

 1 public class OrderController : ApiController
 2 {
 3     private IOrderAnticorrsive orderAnticorrsive;
 4
 5     public OrderController(IOrderAnticorrsive orderAnticorrsive)
 6     {
 7         this.orderAnticorrsive = orderAnticorrsive;
 8
 9         this.orderAnticorrsive.SetController(this);//设置控制器到防腐对象中
10     }
11
12     public event EventHandler<OrderViewModel> SubmitOrderEvent;
13
14     [HttpGet]
15     public void SubmitOrder(OrderViewModel order)
16     {
17         this.SubmitOrderEvent(this, order);
18     }
19 }

在控制器中,每当我们发生某个业务动作时只管触发事件即可,当然主要是以发送数据为主,查询可以直接调用对象的方法。因为防腐对象起到一个与后台服务集成的桥梁,当提交订单时可能需要同时调用很多个后台服务方法,用事件处理会比较方便。

 1     /// <summary>
 2     /// OrderViewModel 防腐对象
 3     /// </summary>
 4     public class OrderAnticorrsive : AnticorrsiveBase<OrderViewModel>, IOrderAnticorrsive
 5     {
 6         public void SetController(OrderController orderController)
 7         {
 8             orderController.SubmitOrderEvent += orderController_SubmitOrderEvent;
 9         }
10
11         private void orderController_SubmitOrderEvent(object sender, OrderViewModel e)
12         {
13             //提交订单的逻辑
14         }
15     }
16 }

6.2.依赖注入接口

依赖注入接口是完全为了将控制器与防腐对象之间隔离用的,上述代码中我是将接口定义在了防腐对象层中,那么也就是说控制器对象所在的项目需要引用防腐层,在处理事件和方法同时使用时会显得有点不伦不类的,既有接口又有方法,其实这就是一种平衡吧,越纯粹的东西越要付出一些代价。

如果我们定义纯粹的依赖注入接口让防腐对象去实现,那么在触发事件时就需要专门的方法来执行事件的触发,因为不在本类中的事件是没办法触发的。

7.总结

本篇文章是我对在UI层使用防腐层架构设计思想的一个简单总结,目的只有一个,提供一个参考,谢谢大家。

作者:王清培

出处:http://www.cnblogs.com/wangiqngpei557/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面

时间: 2024-10-14 19:04:47

.NET应用架构设计—用户端的防腐层作用及设计的相关文章

架构设计--用户端全http参数接口详细说明v1

1. 用户端全http参数接口详细说明v1.doc 1 2. change histor 1 3. 接口通用参数说明 1 4. 函数注册接口(规划中) 3 5. 用户权限模块 3 5.1. 用户注册接口(增加用户登陆数据) 3 5.2. 登陆接口(查询用户登陆权限数据接口) 4 6. 用户信息模块 5 6.1. 修改用户信息 5 7. 商品模块 5 7.1. 调用范例 5 7.2. 显示与查询商品列表接口 6 7.3. 显示商品详情(显示一条商品数据) 7 8. 订单模块接口 7 8.1. 添加

架构设计--用户端全http參数接口具体说明v1

1. 用户端全http參数接口具体说明v1.doc 1 2. change histor 1 3. 接口通用參数说明 1 4. 函数注冊接口(规划中) 3 5. 用户权限模块 3 5.1. 用户注冊接口(添加用户登陆数据) 3 5.2. 登陆接口(查询用户登陆权限数据接口) 4 6. 用户信息模块 5 6.1. 改动用户信息 5 7. 商品模块 5 7.1. 调用范例 5 7.2. 显示与查询商品列表接口 6 7.3. 显示商品详情(显示一条商品数据) 7 8. 订单模块接口 7 8.1. 加入

回归架构本真:从规划、思维到设计,构建坚不可摧的架构根基

一.什么是架构 关于什么是架构,业界从来没有一个统一的定义.Martin Fowler在<企业应用架构模式>中也没有对其给出定义,只是提到能够统一的内容有两点: 最高层次的系统分解: 系统中不易改变的决定. <软件架构设计>一书则将架构定义总结为组成派和决策派: 组成派:架构=组件+交互:软件系统的架构将系统描述为计算组件及组件之间的交互. 决策派:架构=重要决策集:软件架构是在一些重要方面所作出的决策的集合. 而架构的概念最初来源于建筑,因此,我想从建筑的角度去思考这个问题.Wi

MySQL性能调优与架构设计——第9章 MySQL数据库Schema设计的性能优化

MySQL性能调优与架构设计——第9章 MySQL数据库Schema设计的性能优化 前言: 很多人都认为性能是在通过编写代码(程序代码或者是数据库代码)的过程中优化出来的,其实这是一个非常大的误区.真正影响性能最大的部分是在设计中就已经产生了的,后期的优化很多时候所能够带来的改善都只是在解决前妻设计所遗留下来的一些问题而已,而且能够解决的问题通常也比较有限.本章将就如何在 MySQL 数据库 Schema 设计的时候保证尽可能的高效,尽可能减少后期的烦恼. 9.1 高效的模型设计 最规范的就一定

Hadoop项目实战-用户行为分析之分析与设计

1.概述 本课程的视频教程地址:<用户行为分析之分析与设计> 下面开始本教程的学习,本教程以用户行为分析案例为基础,带着大家对项目的各个指标做详细的分析,对项目的整体设计做合理的规划,让大家能通过本课程掌握Hadoop项目的分析与设计.该课程主要包含以下课时: 他们分别是:项目整体分析,项目指标与数据源分析以及项目整体设计.如下图所示: 首先我们来学习第一课时:<项目整体分析>. 2.内容 2.1 项目整体分析 本课时简述分析一个项目产生的背景,以及该项目能给企业带来那些良好的结果

高端智能计算硬件平台开发设计 ——北京太速科技有限公司

北京太速科技有限公司,公司成立于2010年,总部位于北京亚运村.致力于高端智能计算硬件平台开发设计. 公司全力服务于研究所.高校.大型企业的科研硬件平台开发,以PMP项目管理流程实现公司知识管理.风险控制.工艺优化.实现项目的费用.进度.范围.质量的完美统一.以先进制造理念,完善的设计工艺流程和稳健的过程控制,为客户实现稳定.可靠的硬件设计服务.公司长期积累形成的智能图像计算平台.软件无线电处理平台.智能工业设备.服务器加速计算卡已经广泛运用于各领域,深受专家们的好评.公司技术骨干主要毕业于北京

移动端界面中的版式设计原理(上)

移动端界面中的版式设计原理(上) --安阳师范学院互联网+应用技术学院UI设计方向讲师 黄艳芳设计本身就是一门理性的学科,审美因人而异,只有言之有理的设计才能够说服别人.当设计师拿到产品的原型开始做设计时,如果只是单纯的按照原型进行而不考虑任何规则,那么很多时候就会产生一些不协调的结果.设计完之后产品不满意,自己也不满意.在UI设计中其实也存在大量的版式设计原理,如果产品和设计都能对版式设计有一定的了解,那么设计师拿到原型的时候,评审设计输出稿的时候大家都能更好地理解对方的设计.以下我总结了几种

[ASP NET MVC] 使用ReportViewer执行用户端报表定义文件(.rdlc)产出报表

使用ReportViewer执行用户端报表定义文件(.rdlc)来产出报表 前言 以往使用ASP.NET WebForm进行网站开发时,笔者面对报表的产出多会使用ReportViewer来进行,并且搭配用户端报表定义文件(.rdlc)来设计报表外观,其实是相当灵活的解决方案:如今使用ASP.NET MVC进行开发,虽然View中无法加入任何WebForm Control了,但我们依旧可以建立一个共用WebForm页面,在此页面上加入熟悉的ReportViewer来协助产出报表.详细实践细节请参考

GPS部标监控平台的架构设计(八)-基于WCF的平台数据通信设计

总体来讲,GPS部标平台的软件开发是一个对网络通信和应用程序之间通信的技术应用密集型的开发工作,也是有一定设计技术含量的工作. 1.设计通信接口 在设计的时候,根据职责划分,拆分成不同的应用子系统,对各个子系统进行功能隔离,并通过设计接口规定子系统直接的调用规约. 首先我们根据部标平台的要求,设计和开发出各个主要的服务器子系统,这是平台中最核心的子系统,在实际的应用中,由于车辆规模的大小和行业需求,还会扩展出各种业务子系统.核心子系统如下: 1)808GPS服务器,采用交通部的部标808协议,负