.NET框架设计(常被忽视的框架设计技巧)

阅读目录:

  • 1.开篇介绍
  • 2.元数据缓存池模式(在运行时构造元数据缓存池)
    • 2.1.元数据设计模式(抽象出对数据的描述数据)
    • 2.2.借助Dynamic来改变IOC、AOP动态绑定的问题
    • 2.3.元数据和模型绑定、元数据应该隐藏在Model背后、元数据与DSL的关系
  • 3.链式配置Dynamic模式(爱不释手的思维习惯编程)
  • 4.委托工厂模式(要优于常见的 工厂,概念更加准确,减少污染)
  • 5.规则外挂(视委托为特殊的规则对象原型)

1】开篇介绍

通过上一篇的“.NET框架设计—常被忽视的C#设计技巧”一文来看,对于框架设计的技巧还是有很多人比较有兴趣的,那么框架设计思想对于我们日常开发来说其实并不是很重要,但是对于我们理解框架背后的运行原理至关重要;当我们使用着LINQ灵活的语法的同时我们是否能理解它的背后运行原理、设计原理更深一点就是它的设计模式及复杂的对象模型;

从一开始学习.NET我就比较喜欢框架背后的设计模型,框架提供给我们的使用接口是及其简单的,单纯从使用上来看我们不会随着对框架的使用时间而增加我们对框架内部设计的理解,反而会养成一样拿来即用的习惯,我们只有去了解、深挖它的内部设计原理才是我们长久学习的目标;因为框架的内部设计模式是可以提炼出来并被总结的;

 

这篇文章总结了几个我最近接触的框架设计思想,可以称他们为模式;由于时间关系,这里只是介绍加一个简单的介绍和示例让我们能基本的了解它并且能在日后设计框架的时候想起来有这么一个模式、设计方式可以借鉴;当然,这每一节都是一个很大主题,用的时候在去细心的分析学习吧;

2】元数据缓存池模式(在运行时构造元数据缓存池)

很多框架都有将特性放在属性上面用来标识某种东西,但是这种方式使用不当的话会对性能造成影响;再从框架设计原则来讲也是对DomainModel极大的污染,从EntityFramework5.0之前的版本我们就能体会到了,它原本是将部分Attribute加在了Entity上的,但是这毕竟是业务代码的核心,原则上来说这不能有任何的污染,要绝对的POJO;后来5.0之后就完全独立了DomainModel.Entity,所有的管理都在运行时构造绑定关系,因为它有EDMX元数据描述文件;

那么这些Attribute其实本质是.NET在运行时的一种元数据,主要的目的是我们想在运行时将它读取出来,用来对某些方面的判断;那么现在的问题是如果我们每次都去读取这个Attribute是必须要走反射机制,当然你可以找一些框架来解决这个问题;(我们这里讨论的是你作为开发框架的设计者!)

反射影响性能这不用多讲了,那么常规的做法是会在第一次反射之后将这些对象缓存起来,下次再用的时候直接在缓存中读取;这没有问题,这是解决了反射的性能问题,那么你的Attribute是否还要加在DomainModel中呢,如果加的话随着代码量的增加,这些都会成为后面维护的成本开销;那么我们如何将干净的POJO对象提供给程序员用,但是在后台我们也能对POJO进行强大的控制?这是否是一种设计问题?

2.1】元数据设计模式(抽象出对数据的描述数据)

我一直比较关注对象与数据之间的关系,面向对象的这种纵横向关系如何平滑的与E-R实体关系模型对接,这一直是复杂软件开发的核心问题;这里就用它来作为本章的示例的基本概要;

我们有一个基本的DomainModel聚合,如何在不影响本身简洁性的情况下与E-R关系对接,比如我们在对聚合进行一个Add操作如何被映射成对数据库的Insert操作;我们来看一下元数据设计模式思想;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ConsoleApplication1.DomainModel
 9 {
10     /// <summary>
11     /// Employee.<see cref="DomainModel.Employee"/>
12     /// </summary>
13     public class Employee
14     {
15         /// <summary>
16         /// Primary id.
17         /// </summary>
18         public string EId { get; set; }
19
20         /// <summary>
21         /// Name.
22         /// </summary>
23         public string Name { get; set; }
24
25         /// <summary>
26         /// Sex.<see cref="DomainModel.SexType"/>
27         /// </summary>
28         public SexType Sex { get; set; }
29
30         /// <summary>
31         /// Address.
32         /// </summary>
33         public Address Address { get; set; }
34     }
35 }

View Code

这里有一个以Employee实体为聚合根的聚合,里面包含一些基本的属性,特别需要强调的是Sex属性和Address,这两个属性分别是Complex类型的属性;
Complex类型的属性是符合面向对象的需要的,但是在关系型数据库中是很难实现的,这里就需要我们用元数据将它描述出来并能在一些行为上进行控制;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ConsoleApplication1.DomainModel
 9 {
10     /// <summary>
11     /// Address .
12     /// </summary>
13     public struct Address
14     {
15         /// <summary>
16         /// Address name.
17         /// </summary>
18         public string AddressName { get; set; }
19     }
20 }

View Code

这是Address类型的定义;

 1 namespace ConsoleApplication1.DomainModel
 2 {
 3     /// <summary>
 4     /// Sex type.
 5     /// </summary>
 6     public enum SexType
 7     {
 8         Male,
 9         Female
10     }
11 }

View Code

这是SexType类型的定义;都比较简单;  

只有这样我们才能对DomainModel进行大面积的复杂设计,如果我们不能将数据对象化我们无法使用设计模式,也就谈不上扩展性;

图1:

这是我们的对象模型,那么我们如何将它与数据库相关的信息提取出来形成独立的元数据信息,对元数据的抽取需要动、静结合才行;

什么动、静结合,我们是否都会碰见过这样的问题,很多时候我们的代码在编译时是确定的,但是有部分的代码需要在运行时动态的构造,甚至有些时候代码需要根据当前的IDE来生成才行,但是最终在使用的时候这些在不同阶段生成的代码都需要结合起来变成一个完整的元数据对象;

框架在很多时候需要跟IDE结合才能使使用变的顺手,比如我们在开发自己的ORM框架如果不能直接嵌入到VisualStudio中的话,用起来会很不爽;当我们用自己的插件去连接数据库并且生成代码的时候,有部分的元数据模型已经在代码中实现,但是有部分需要我们动态的去设置才行;

我们来看一下关于元数据的基础代码;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ORM.Meta
 9 {
10     using System;
11     using System.Collections.Generic;
12     using System.Linq;
13     using System.Text;
14     using System.Threading.Tasks;
15
16     /// <summary>
17     /// Data source context.
18     /// </summary>
19     public abstract class DataBaseContext : List<MetaTable>, IDisposable
20     {
21         /// <summary>
22         /// Data base name.
23         /// </summary>
24         protected string DataBaseName { get; set; }
25
26         /// <summary>
27         /// Connection string.
28         /// </summary>
29         protected string ConnectionString { get; set; }
30
31         /// <summary>
32         /// Provider child class add table.
33         /// </summary>
34         /// <param name="table"></param>
35         protected virtual void AddTable(MetaTable table)
36         {
37             this.Add(table);
38         }
39
40         /// <summary>
41         /// Init context.
42         /// </summary>
43         protected virtual void InitContext() { }
44         public void Dispose() { }
45     }
46 }

View Code

这表示数据源上下文,属于运行时元数据的基础设施;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ORM.Meta
 9 {
10     using System.Collections.Generic;
11     using System.Linq;
12
13     /// <summary>
14     /// Database Table meta.
15     /// </summary>
16     public class MetaTable : List<MetaColumn>
17     {
18         /// <summary>
19         /// Table name.
20         /// </summary>
21         public string Name { get; set; }
22
23         /// <summary>
24         /// Entity name.
25         /// </summary>
26         public string EntityName { get; set; }
27
28         /// <summary>
29         /// Get column by column name.
30         /// </summary>
31         /// <param name="name">Column name.</param>
32         /// <returns><see cref="ORM.MetaColumn"/></returns>
33         public MetaColumn GetColumnByName(string name)
34         {
35             var column = from item in this.ToList() where item.CoumnName == name select item;
36             return column.FirstOrDefault();
37         }
38     }
39 }

View Code

简单的表示一个Table,里面包含一系列的Columns;要记住在设计元数据基础代码的时候将接口留出来,方便在IDE中植入初始化元数据代码;

图2:

到目前为止我们都是在为元数据做基础工作,我们看一下有系统生成的声明的元数据代码;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ConsoleApplication1.Repository
 9 {
10     using System;
11     using System.Collections.Generic;
12     using System.Linq;
13     using System.Text;
14     using System.Threading.Tasks;
15
16     /// <summary>
17     /// IDE Builder.
18     /// </summary>
19     public class DesignBuilder_DataBaseContext : ORM.Meta.DataBaseContext
20     {
21         //this begin IDE builder.
22         protected override void InitContext()
23         {
24             ORM.Meta.MetaTable metaTable = new ORM.Meta.MetaTable() { Name = "TB_Employee", EntityName = "Employee" };
25             metaTable.Add(new ORM.Meta.MetaColumn()
26             {
27                 CoumnName = "EId",
28                 DataType = ORM.Meta.DataType.NVarchar
29             });
30             metaTable.Add(new ORM.Meta.MetaColumn()
31             {
32                 CoumnName = "Name",
33                 DataType = ORM.Meta.DataType.NVarchar
34             });
35             metaTable.Add(new ORM.Meta.MetaColumn()
36             {
37                 CoumnName = "Sex",
38                 DataType = ORM.Meta.DataType.Int
39             });
40             metaTable.Add(new ORM.Meta.MetaColumn()
41             {
42                 CoumnName = "Address",
43                 DataType = ORM.Meta.DataType.NVarchar
44             });
45             this.AddTable(metaTable);
46         }
47         //end
48     }
49 }

View Code

我假设这是我们框架在IDE中生成的部分元数据代码,当然你可以用任何方式来存放这些元数据,但是最后还是要去对象化;

图3:

这个目录你可以直接隐藏,在后台属于你的框架需要的一部分,没有必要让它污染项目结构,当然放出来也有理由;如果想让你的LINQ或者表达式能直接穿过你的元数据上下文你需要直接扩展;

1 static void Main(string[] args)
2         {
3             using (Repository.DesignBuilder_DataBaseContext context = new Repository.DesignBuilder_DataBaseContext())
4             {
5                 var employee = from emp in context.Employee where emp.EId == "Wqp123" select emp;
6             }
7         }

View Code

 

这里所有的代码看上去很简单,没有多高深的技术,这也不是本篇文章的目的,任何代码都需要设计的驱动才能产生价值,我们构建的基础代码都是元数据驱动;当你在运行时把这些元数据放入Cache,既不需要加Attribute也不需要反射反而活动了更大程度上的控制,但是要想构建一个能用的元数据结构需要结合具体的需求才行;

2.2】借助Dynamic来改变IOC、AOP动态绑定的问题

要想在运行时完全动态的绑定在编译时定义的对象行为是需要强大的IOC框架支撑的,这样的框架我们是做不来的或者需要很多精力,得不偿失;对于元数据设计需要将AOP通过IOC的方式注入,在使用的时候需要改变一下思路,AOP的所有的切面在编译时无法确定,后期通过IOC的方式将所有的行为注入;这里我们需要使用动态类型特性;

使用Dynamic之后我们很多以往不能解决问题都可以解决,更向元编程跨进了一步;对于IOC、AOP的使用也将变的很简单,也有可能颠覆以往IOC、AOP的使用方式;而且动态编程将在很大程度上越过设计模式了,也就是设计模式的使用方式在动态编程中将不复存在了;

 1 using (Repository.DesignBuilder_DataBaseContext context = new Repository.DesignBuilder_DataBaseContext())
 2             {
 3                 var employees = from emp in context.Employee where emp.EId == "Wqp123" select emp;
 4
 5                 Employee employee = new Employee() { EId = "Wqp123" };
 6
 7                 var entityOpeartion = DynamicBehavior.EntityDymanicBehavior.GetEntityBehavior<Employee>(employee);
 8
 9                 entityOpeartion.Add();
10             }

View Code

 

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ConsoleApplication1.DynamicBehavior
 9 {
10     using System;
11     using System.Dynamic;
12
13     public class EntityDymanicBehavior
14     {
15         public static dynamic GetEntityBehavior<TEntity>(TEntity entity)
16         {
17             //auto mark entity behavior
18             dynamic dy = new ExpandoObject();
19             //load meta data mark dynamic behavior
20             dy.Entity = entity;
21             dy.Add = new Action(() =>
22             {
23                 Console.WriteLine("Action Add " + entity.GetType());
24             });
25             return dy;
26         }
27     }
28 }

View Code

图4:

画红线的部分是可以抽取来放入扩展方法Add中的,在构造的内部是完全可以进入到元数据缓存池中拿到这些数据然后直接动态生成扩展方法背后的真实方法;

2.3】元数据和模型绑定、元数据应该隐藏在Model背后、元数据与DSL的关系

元数据的绑定应该在运行时动态去完成,这点在以往我们需要大费力气,通过CodeDom、Emit才能完成,但是现在可以通过Dynamic、DLR来完成;思维需要转变一下,动态编程我们以往用的最多的地方在JS上,现在可以在C#中使用,当然你也可以使用专门的动态语言来写更强大的元数据框架,IronRuby、IronPython都是很不错的,简单的了解过Ruby的元数据编程,很强大,如果我们.NET程序员眼馋就用Iron…系列;

在开发复杂的动态行为时尽量使用元数据设计思想,不要把数据和表示数据的数据揉在一起,要把他们分开,在运行时Dynamic绑定;元数据应该在Model的背后应该在DomainModel的背后;

元数据和DSL有着天然的渊源,如果我们能把所有的语句组件化就可以将其封入.NET组件中,在IDE中进行所见即所得的DSL设计,然后生成可以直接运行的Dynamic代码,这可能也是元编程的思想之一吧;

图5:

这可能是未来10年要改变的编程路线吧,我只是猜测;最后软件将进一步被自定义;

3】链式配置Dynamic模式(爱不释手的思维习惯编程)

再一次提起链式编程是觉得它的灵活性无话可说,语言特性本身用在哪里完全需求驱动;把链式用来做配置相关的工作非常的合适;我们上面做了元数据配置相关的工作,这里我们试着用链式的方法来改善它;

Dynamic类型本身的所有行为属性都是可以动态构建的,那么我们把它放入链式的方法中去,根据不同的参数来实现动态的添加行为;

扩展Dynamic类型需要使用ExpandoObject开始;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ConsoleApplication1.DynamicBehavior
 9 {
10     using System;
11     using System.Dynamic;
12
13     public static class EntityDynamicBehaviorExtent
14     {
15         /// <summary>
16         /// Add dynamic method.
17         /// </summary>
18         /// <param name="entity"></param>
19         /// <returns></returns>
20         public static ExpandoObject AddExten(this ExpandoObject entity)
21         {
22             dynamic dy = entity as dynamic;
23             dy.Add = new Func<ExpandoObject>(() => { Console.WriteLine("add " + entity); return entity; });
24             return entity;
25         }
26         /// <summary>
27         /// where  dynamic method.
28         /// </summary>
29         /// <typeparam name="T"></typeparam>
30         /// <param name="entity"></param>
31         /// <param name="where"></param>
32         /// <returns></returns>
33         public static ExpandoObject WhereExten<T>(this ExpandoObject entity, Func<T, bool> where)
34         {
35             dynamic dy = entity as dynamic;
36             dy.Where = where;
37             return entity;
38         }
39     }
40 }

View Code

扩展方法需要扩展 ExpandoObject对象,DLR在运行时使用的是ExpandoObject对象实例,所以我们不能够直接扩展Dynamic关键字;

1 Employee employee1 = new Employee() { EId = "Wqp123" };
2                 var dynamicEntity = DynamicBehavior.EntityDymanicBehavior.GetEntityBehavior<Employee>(employee1);
3                 (dynamicEntity as System.Dynamic.ExpandoObject).AddExten().WhereExten<Employee>(emp =>
4                 {
5                     Console.WriteLine("Where Method.");
6                     return emp.EId == "Wqp123";
7                 });
8                 dynamicEntity.Add().Where(employee1);

View Code

 图6:

红线部分必须要转换才能顺利添加行为;

4】委托工厂模式(要优于常见的 工厂,概念更加准确,减少污染)

对于工厂模式我们都会熟悉的一塌糊涂,各种各样的工厂模式我们见的多了,但是这种类型的工厂使用方式你还真的没见过;其实这种委托是想部分的逻辑交给外部来处理;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ConsoleApplication1.DomainModel
 9 {
10     /// <summary>
11     /// Address factory.
12     /// </summary>
13     /// <returns></returns>
14     public delegate Address Factory();
15
16     /// <summary>
17     /// Employee.<see cref="DomainModel.Employee"/>
18     /// </summary>
19     public class Employee
20     {
21         public Employee() { }
22         /// <summary>
23         /// Mark employee instance.
24         /// </summary>
25         /// <param name="eID"></param>
26         /// <param name="name"></param>
27         /// <param name="sex"></param>
28         /// <param name="addressFactory">address factory.</param>
29         public Employee(string eID, string name, SexType sex, Factory addressFactory)
30         {
31             this.EId = eID;
32             this.Name = name;
33             this.Sex = sex;
34             this.Address = addressFactory();
35         }
36         /// <summary>
37         /// Primary id.
38         /// </summary>
39         public string EId { get; set; }
40
41         /// <summary>
42         /// Name.
43         /// </summary>
44         public string Name { get; set; }
45
46         /// <summary>
47         /// Sex.<see cref="DomainModel.SexType"/>
48         /// </summary>
49         public SexType Sex { get; set; }
50
51         /// <summary>
52         /// Address.
53         /// </summary>
54         public Address Address { get; set; }
55     }
56 }

View Code

我们定义了一个用来创建Employee.Address对象的Factory,然后通过构造函数传入;

1 Employee employee2 = new Employee("Wqp123", "Wqp", SexType.Male, new Factory(() =>
2                 {
3                     return new Address() { AddressName = "Shanghai" };
4                 }));

View Code

这里纯粹为了演示方便,这种功能是不应该在DommianModel中使用的,都是在一些框架、工具中用来做灵活接口用的;

5】规则外挂(视委托为特殊的规则对象原型)

规则外挂其实跟上面的委托工厂有点像,但是绝对不一样的设计思想;如何将规则外挂出去,放入Cache中让运行时可以配置这个规则参数;委托是规则的天然宿主,我们只要将委托序列化进Cache就可以对它进行参数的配置;

 1 /*==============================================================================
 2  * Author:深度训练
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定领域软件工程实践;
 6  *==============================================================================*/
 7
 8 namespace ConsoleApplication1.DomainModel.Specification
 9 {
10     using System;
11     using System.Linq.Expressions;
12
13     /// <summary>
14     /// Employee add specification.
15     /// </summary>
16     [Serializable]
17     public class EmployeeSpecificationAdd : System.Runtime.Serialization.IDeserializationCallback
18     {
19         /// <summary>
20         /// specification.
21         /// </summary>
22         [NonSerialized]
23         private Func<Employee, bool> _specification;
24         /// <summary>
25         /// Gets specification.
26         /// </summary>
27         public Func<Employee, bool> Specificaion { get { return _specification; } }
28
29         /// <summary>
30         /// employee.
31         /// </summary>
32         private Employee Employee { get; set; }
33
34         /// <summary>
35         /// Mark employee specificatoin.
36         /// </summary>
37         /// <param name="employee"></param>
38         public EmployeeSpecificationAdd(Employee employee)
39         {
40             this.Employee = employee;

41             InitSpecification();
42         }
43         /// <summary>
44         /// Is Check.
45         /// </summary>
46         /// <returns></returns>
47         public bool IsCheck()
48         {
49             return _specification(Employee);
50         }
51
52         public void OnDeserialization(object sender)
53         {
54             InitSpecification();
55         }
56         private void InitSpecification()
57         {
58             this._specification = (emp) =>
59             {
60                 return !string.IsNullOrWhiteSpace(emp.EId) && !string.IsNullOrWhiteSpace(emp.Name);
61             };
62         }
63     }
64 }

View Code

图7:

注意这里的反序列化接口实现,因为Lambda无法进行序列化,也没有必要进行序列化;

 1 EmployeeSpecificationAdd specification = new EmployeeSpecificationAdd(employee2);
 2
 3                 Stream stream = File.Open("specification.xml", FileMode.Create);
 4                 BinaryFormatter formattter = new BinaryFormatter();
 5                 formattter.Serialize(stream, specification);
 6
 7                 stream.Seek(0, SeekOrigin.Begin);
 8                 specification = formattter.Deserialize(stream) as EmployeeSpecificationAdd;
 9
10                 stream.Close();
11                 stream.Dispose();
12                 if (specification.IsCheck())
13                 {
14                     Console.WriteLine("Ok...");
15                 }

View Code

既然能将规则序列化了,就可以把它放在任何可以使用的地方了,配置化已经没有问题了;

示例Demo地址:http://files.cnblogs.com/wangiqngpei557/ConsoleApplication2.zip

 

作者:王清培

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

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

时间: 2024-10-26 15:21:26

.NET框架设计(常被忽视的框架设计技巧)的相关文章

.NET框架设计(常被忽视的C#设计技巧)

阅读目录: 1.开篇介绍 2.尽量使用Lambda匿名函数调用代替反射调用(走进声明式设计) 3.被忽视的特性(Attribute)设计方式 4.扩展方法让你的对象如虎添翼(要学会使用扩展方法的设计思想) 5.别怕Static属性(很多人都怕Static在Service模式下的设计,其实要学会使用线程本地存储(ThreadStatic)) 6.泛型的协变与逆变(设计架构接口(Interface)时要时刻注意对象的协变.逆变) 7.使用泛型的类型推断(还在为参数类型烦恼吗) 8.链式编程(设计符合

百度竞价推广常被忽视的三个技巧

由于http://www.aliyun.com/zixun/aggregation/8150.html">传统行业竞争越来越大,很多企业都走 网络销售之路.目前越来越多的企业可以开始做百度竞价,百度竞价是一个很高效推广的方式,客户精准,见效快. 但是百度竞价对于一些不懂百度竞价的企业那算是烧钱的机器.如果麻木的做百度竞价,小心烧钱死你.目前减肥行业竞争 激烈,现在这个词点击价格高达80几,如果你把关键词设为广泛匹配,你的钱会很快就烧完,成交率大大降低.怎么才能做好百度竞价呢?小脑袋竞价软件

互联网产品设计:优化信息架构和框架图

文章描述:框架图需要的东西很简单,在这样的电子系统中,使用相近的元素去表达界面最终的大致展现,会更容易让人理解,也可以开始进行小范围的用户调查,最终通过不断优化信息架构和框架图,形成策划阶段清晰的交互原型,最终可以进行相关设计.开发等工作. 通过基础的信息架构图,框架图是最能把信息树演变成为真实思维联想的一种方式. 框架图需要的东西很简单,在这样的电子系统中,使用相近的元素去表达界面最终的大致展现,会更容易让人理解,也可以开始进行小范围的用户调查,最终通过不断优化信息架构和框架图,形成策划阶段清

关于网页设计制作中设计师的框架

近来,在Web开发中"框架"是一个相当时髦的词.比如JavaScript 框架 YUI. JQuery和Prototype 都引起广泛的关注, Web应用框架Rails and Dojo 更是引人瞩目,仿佛所有人都使用某种框架来开发自己的网站.但究竟什么是框架?是不是框架仅仅是对程序员有用,设计师是否可以从中收益? 什么是框架? 为了便于沟通,我们给"框架"统一一个定义(至少在本篇文章中是统一的):一套包含工具.函数库.约定,以及尝试从常用任务中抽象出可以复用的通用

《 产品设计思维:电商产品设计全攻略》一一1.3 有效的设计框架

1.3 有效的设计框架 没有最好的设计,只有最适合的设计,基于场景化的设计永远是核心.1.3.1 设计的思维定式 当认识到设计是一个"为达到某个目的而刻意进行的创造行为"时,我们就会明白,设计是一种特殊的技能,这个技能的门槛很低,甚至于人人都在不断地应用设计技能,比如你每天出门时构想一个不会迟到的交通路线,是坐地铁还是乘出租车,如何避开高峰路段,如何以更快更节省成本的方式到达目的地,构想的这个过程,实际上也是在做设计.但是真正优秀的设计总是少之又少,为什么? 我认为,关键的原因在于,大

非常实用的js验证框架实现源码 附原理方法_javascript技巧

本文为大家分享一个很实用的js验证框架实现源码,供大家参考,具体内容如下 关键方法和原理: function check(thisInput) 方法中的 if (!eval(scriptCode)) { return false; } 调用示例: 复制代码 代码如下: <input type="text" class="text_field percentCheck" name="progress_payment_two" id="

对Web开发中前端框架与前端类库的一些思考_javascript技巧

说起前端框架,我也是醉了.现在去面试或者和同行聊天,动不动就这个框架碉堡了,那个框架好犀利. 当然不是贬低框架,只是有一种杀鸡焉用牛刀的感觉.网站技术是为业务而存在的,除此毫无意义,框架也是一样.在技术选型和架构设计当中,脱离网站业务发展的实际,一味的追求时髦新技术,可能会适得其反,将网站发展引入崎岖小道.就好像一个日均pv只有几百的小型电商网站,却要大喊"某宝就是这么搞的",然后搭建应用服务器集群,使用分布式文件系统和分布式数据库系统...等巴拉巴拉的一堆用来处理高并发,海量数据访问

javascript实现框架高度随内容改变的方法_javascript技巧

本文实例讲述了javascript实现框架高度随内容改变的方法.分享给大家供大家参考.具体如下: 有两种方法: 一.就是通过父页面改变 这里要理解框架的两个属性 contentWindow 和contentDocument 两个属性的意思和window document意思差不多,不同的是contentWindow 所有浏览器都支持,contentDocument   ie6,7不支持,chrome 也不支持 <iframe onload="change_height()">

Entity Framework 实体框架的形成之旅--实体框架的开发的几个经验总结

在前阵子,我对实体框架进行了一定的研究,然后把整个学习的过程开了一个系列,以逐步深入的方式解读实体框架的相关技术,期间每每碰到一些新的问题需要潜入研究.本文继续前面的主题介绍,着重从整体性的来总结一下实体框架的一些方面,希望针对这些实际问题,和大家进行学习交流. 我的整个实体框架的学习和研究,是以我的Winform框架顺利升级到这个实体框架基础上为一个阶段终结,这个阶段事情很多,从开始客运联网售票的WebAPI平台的开发,到微软实体框架的深入研究,以及<基于Metronic的Bootstrap开