ASP.NET ViewState初探

话说在新公司上班快2周了,这里的工作和业务还是有些意思和挑战的。这几天1直在重构公司之前的一个项目,出于保密协议这里不能提项目名称,作为对新人的关爱,我仅负责其中1个模块的1个页面,这个页面的性能有些问题,当大批量进行数据修改的时候,提交和响应会非常的慢,我需要做的是就是改善它的性能。

花了一天时间分析了这个页面的有关代码,实在很头痛,大量的服务端控件、大量的事件、大量的重复代码,发现问题的关键在于该页大量的服务端控件(GridView嵌套大量DropDownList、TextBox、CheckBox)及大量的迭代查询。

于是狠心下来进行重构,越深入发现问题越多,根本无法重构,除非整个模块和某些底层推倒重来,但是工时将纠人心窝(明早开会把情况说明PM估计要跳的了)。

这个项目通过微软传统的WebForm Code Behind编程模型,大量使用事件和服务端控件,说到服务端控件,有个很重要的属性EnableViewState,它保持着页面回转的状态,借这次Code Review的机会,将Asp.Net ViewState进行一次复习。

ASP.NET ViewState 初探


ViewState 到底是什么

与刚接触 ASP.NET 页面的开发人员交谈时,他们通常向我提出的第一个问题就是:“那个 ViewState 到底是什么?”他们的语气中流露出的那种感觉,就象我来到一家异国情调的餐馆,侍者端上一道我从未见过的菜肴时的那种感觉 —— 既疑惑不解,又充满好奇。但肯定有人认为它不错,否则就不会提供了。所以,我会先尝一尝,或许会喜欢上它,尽管它看上去的确很古怪!
对于 ViewState 也是如此,但是如果适应了它的风格,您会发现在许多情况下,您将乐于在自己的 ASP.NET 应用程序中使用 ViewState,因为它可以帮助您使用更少的代码完成更多的工作。但是,有时也会对 ViewState 完全弃之不用。下面我们就这两种情况分别进行阐述,不过,让我们先回答什么是 ViewState 这个问题。
答案:ViewState 用于维护页面的 UI 状态
Web 是没有状态的,ASP.NET 页面也没有状态,它们在到服务器的每个往返过程中被实例化、执行、呈现和处理。作为 Web 开发人员,您可以使用众所周知的技术(如以会话状态将状态存储在服务器上,或将页面回传到自身)来添加状态。下面我们以图 1 中的注册窗体为例进行论述。

Continue reading “ASP.NET ViewState初探”

多线程应用中使用静态方法是否有线程安全问题

类的成员分为两类,静态成员(static member)和实例成员(instance member)。静态成员属于类,实例成员则属于对象,即类的实例。

简单讨论一下在一个类中使用静态字段(static field)和静态方法(static method)是否会有线程安全问题。

我们知道, 静态字段(static field)和静态方法(static method)的调用是通过类来调用。静态方法不对特定的实例操作。实例方法可对特定的实例操作,既能访问静态成员,也能访问实例成员。

那么,在多线程中使用静态方法是否有线程安全问题?这要看静态方法是是引起线程安全问题要看在静态方法中是否使用了静态成员。

因为,在多线程中使用同一个静态方法时,每个线程使用各自的实例字段(instance field)的副本,而共享一个静态字段(static field)。所以说,如果该静态方法不去操作一个静态成员,只在方法内部使用实例字段(instance field),不会引起安全性问题。但是,如果该静态方法操作了一个静态字段,则需要静态方法中采用互斥访问的方式进行安全处理。

静态变量前加volitile,静态函数里面使用Lock控制线程安全。

对于ASP.NET Web应用程序, 多个客户端访问服务端, 就是一个多线程的例子。

C#运算符关键字 – AS运算符

从这一篇开始,我将为大家带来一些C#运算符关键字,我们时常会用到它们,但是可能只知其然不知其所以然,所以整理出来,供大家参考:

as 运算符用于在兼容的引用类型之间执行某些类型的转换。 例如:

 class csrefKeywordsOperators
   {
       class Base
       {
           public override string  ToString()
           {
	             return "Base";
           }
       }
       class Derived : Base 
       { }

       class Program
       {
           static void Main()
           {

               Derived d = new Derived();

               Base b = d as Base;
               if (b != null)
               {
                   Console.WriteLine(b.ToString());
               }

           }
       }
   }

备注


as 运算符类似于强制转换操作。 但是,如果无法进行转换,则 as 返回 null 而非引发异常。 请看下面的表达式:

expression as type

它等效于以下表达式,但只计算一次 expression。

expression is type ? (type)expression : (type)null

注意,as 运算符只执行引用转换和装箱转换。 as 运算符无法执行其他转换,如用户定义的转换,这类转换应使用强制转换表达式来执行。

 

class ClassA { }
class ClassB { }

class MainClass
{
    static void Main()
    {
        object[] objArray = new object[6];
        objArray[0] = new ClassA();
        objArray[1] = new ClassB();
        objArray[2] = "hello";
        objArray[3] = 123;
        objArray[4] = 123.4;
        objArray[5] = null;

        for (int i = 0; i < objArray.Length; ++i)
        {
            string s = objArray[i] as string;
            Console.Write("{0}:", i);
            if (s != null)
            {
                Console.WriteLine("'" + s + "'");
            }
            else
            {
                Console.WriteLine("not a string");
            }
        }
    }
}
/*
Output:
0:not a string
1:not a string
2:'hello'
3:not a string
4:not a string
5:not a string
*/


在.Net中检查Sql注入

SQL 注入是一种攻击方式,在这种攻击方式中,恶意代码被插入到字符串中,然后将该字符串传递到 SQL Server 的实例以进行分析和执行。任何构成 SQL 语句的过程都应进行注入漏洞检查,因为 SQL Server 将执行其接收到的所有语法有效的查询。一个有经验的、坚定的攻击者甚至可以操作参数化数据。

SQL 注入的主要形式包括直接将代码插入到与 SQL 命令串联在一起并使其得以执行的用户输入变量。一种间接的攻击会将恶意代码注入要在表中存储或作为元数据存储的字符串。在存储的字符串随后串连到一个动态 SQL 命令中时,将执行该恶意代码。

注入过程的工作方式是提前终止文本字符串,然后追加一个新的命令。由于插入的命令可能在执行前追加其他字符串,因此攻击者将用注释标记“–”来终止注入的字符串。执行时,此后的文本将被忽略。

Continue reading “在.Net中检查Sql注入”

SQL注入

所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击.

目录

  • sql注入
  • 注入大致方法
  • 常用sql注入语句

sql注入

什么时候最易受到sql注入攻击

当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生sql注入。sql注入可能导致攻击者使用应用程序登陆在数据库中执行命令。相关的SQL注入可以通过测试工具pangolin进行。如果应用程序使用特权过高的帐户连接到数据库,这种问题会变得很严重。在某些表单中,用户输入的内容直接用来构造动态sql命令,或者作为存储过程的输入参数,这些表单特别容易受到sql注入的攻击。而许多网站程序在编写时,没有对用户输入的合法性进行判断或者程序中本身的变量处理不当,使应用程序存在安全隐患。这样,用户就可以提交一段数据库查询的代码,根据程序返回的结果,获得一些敏感的信息或者控制整个服务器,于是sql注入就发生了。

Continue reading “SQL注入”

设计模式:单件模式(Singleton Pattern)

概述

Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点。这就提出了一个问题:如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,而不是类使用者的责任。

从另一个角度来说,Singleton模式其实也是一种职责型模式。因为我们创建了一个对象,这个对象扮演了独一无二的角色,在这个单独的对象实例中,它集中了它所属类的所有权力,同时它也肩负了行使这种权力的职责!

意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

模型图

逻辑模型图:

物理模型图:

生活中的例子

美国总统的职位是Singleton,美国宪法规定了总统的选举,任期以及继任的顺序。这样,在任何时刻只能由一个现任的总统。无论现任总统的身份为何,其头衔”美利坚合众国总统”是访问这个职位的人的一个全局的访问点。

五种实现

1.简单实现

public sealed class Singleton
{
    static Singleton instance=null;

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance==null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}

这种方式的实现对于线程来说并不是安全的,因为在多线程的环境下有可能得到Singleton类的多个实例。如果同时有两个线程去判断(instance == null),并且得到的结果为真,这时两个线程都会创建类Singleton的实例,这样就违背了Singleton模式的原则。实际上在上述代码中,有可能在计算出表达式的值之前,对象实例已经被创建,但是内存模型并不能保证对象实例在第二个线程创建之前被发现。

该实现方式主要有两个优点:

  • 由于实例是在 Instance 属性方法内部创建的,因此类可以使用附加功能(例如,对子类进行实例化),即使它可能引入不想要的依赖性。
  • 直到对象要求产生一个实例才执行实例化;这种方法称为“惰性实例化”。惰性实例化避免了在应用程序启动时实例化不必要的 singleton

2.安全的线程

public sealed class Singleton
{
    static Singleton instance=null;
    static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance==null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}
 这种方式的实现对于线程来说是安全的。我们首先创建了一个进程辅助对象,线程在进入时先对辅助对象加锁然后再检测对象是否被创建,这样可以确保只有一个实例被创建,因为在同一个时刻加了锁的那部分程序只有一个线程可以进入。这种情况下,对象实例由最先进入的那个线程创建,后来的线程在进入时(instence == null)为假,不会再去创建对象实例了。但是这种实现方式增加了额外的开销,损失了性能。

3.双重锁定

public sealed class Singleton
{
    static Singleton instance=null;
    static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance==null)
            {
                lock (padlock)
                {
                    if (instance==null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

这种实现方式对多线程来说是安全的,同时线程不是每次都加锁,只有判断对象实例没有被创建时它才加锁,有了我们上面第一部分的里面的分析,我们知道,加锁后还得再进行对象是否已被创建的判断。它解决了线程并发问题,同时避免在每个 Instance 属性方法的调用中都出现独占锁定。它还允许您将实例化延迟到第一次访问对象时发生。实际上,应用程序很少需要这种类型的实现。大多数情况下我们会用静态初始化。这种方式仍然有很多缺点:无法实现延迟初始化。

4.静态初始化

public sealed class Singleton
{
    static readonly Singleton instance=new Singleton();

    static Singleton()
    {
    }

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

看到上面这段富有戏剧性的代码,我们可能会产生怀疑,这还是Singleton模式吗?在此实现中,将在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化。该类标记为sealed 以阻止发生派生,而派生可能会增加实例。此外,变量标记为 readonly,这意味着只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。

该实现与前面的示例类似,不同之处在于它依赖公共语言运行库来初始化变量。它仍然可以用来解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访问实例提供了一个全局访问点。此外,由于构造函数是私有的,因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。

由于 Singleton 实例被私有静态成员变量引用,因此在类首次被对 Instance 属性的调用所引用之前,不会发生实例化。

这种方法唯一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。

5.延迟初始化

public sealed class Singleton
{
    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }

    class Nested
    {
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

这里,初始化工作有Nested类的一个静态成员来完成,这样就实现了延迟初始化,并具有很多的优势,是值得推荐的一种实现方式。

实现要点

  • Singleton模式是限制而不是改进类的创建。
  • Singleton类中的实例构造器可以设置为Protected以允许子类派生。
  • Singleton模式一般不要支持Icloneable接口,因为这可能导致多个对象实例,与Singleton模式的初衷违背。
  • Singleton模式一般不要支持序列化,这也有可能导致多个对象实例,这也与Singleton模式的初衷违背。
  • Singleton只考虑了对象创建的管理,没有考虑到销毁的管理,就支持垃圾回收的平台和对象的开销来讲,我们一般没必要对其销毁进行特殊的管理。
  • 理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的构造器的任意调用”。
  • 可以很简单的修改一个Singleton,使它有少数几个实例,这样做是允许的而且是有意义的。

优点

  • 实例控制:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例
  • 灵活性:因为类控制了实例化过程,所以类可以更加灵活修改实例化过程

缺点

  • 开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题,上面的五种实现方式中已经说过了。
  • 可能的开发混淆:使用 singleton 对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
  • 对象的生存期:Singleton 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于 .NET Framework 的语言),只有 Singleton 类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致 Singleton 类中出现悬浮引用。

适用性

  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

应用场景

  • 每台计算机可以有若干个打印机,但只能有一个Printer Spooler,避免两个打印作业同时输出到打印机。(摘自吕震宇的C#设计模式(7)-Singleton Pattern
  •  PC机中可能有几个串口,但只能有一个COM1口的实例。
  • 系统中只能有一个窗口管理器。
  • .NET Remoting中服务器激活对象中的Sigleton对象,确保所有的客户程序的请求都只有一个实例来处理。

完整示例

这是一个简单的计数器例子,四个线程同时进行计数。

 

using System; 
using System.Threading; 

namespace SigletonPattern.SigletonCounter 
{ 
    /// <summary> 
    /// 功能:简单计数器的单件模式 
    /// 编写:Terrylee 
    /// 日期:2005年12月06日 
    /// </summary> 
    public class CountSigleton 
    { 
        ///存储唯一的实例 
        static CountSigleton uniCounter = new CountSigleton();   

        ///存储计数值 
        private int totNum = 0;   

        private CountSigleton()  

        {  
            ///线程延迟2000毫秒 
            Thread.Sleep(2000); 
        }  

        static public CountSigleton Instance()  

        {  

            return uniCounter;  

        }  

        ///计数加1 
        public void Add() 
        {  
            totNum ++; 
        }   

        ///获得当前计数值 
        public int GetCounter() 
        {  
            return totNum; 
        }  

    } 
}
using System;
using System.Threading;
using System.Text;

namespace SigletonPattern.SigletonCounter
{
    /// <summary>
    /// 功能:创建一个多线程计数的类
    /// 编写:Terrylee
    /// 日期:2005年12月06日
    /// </summary>
    public class CountMutilThread
    {
        public CountMutilThread()
        {

        }

        /// <summary>
        /// 线程工作
        /// </summary>
        public static void DoSomeWork()
        {
            ///构造显示字符串
            string results = "";

            ///创建一个Sigleton实例
            CountSigleton MyCounter = CountSigleton.Instance();

            ///循环调用四次
            for(int i=1;i<5;i++)
            {
                ///开始计数
                MyCounter.Add();

                results +="线程";
                results += Thread.CurrentThread.Name.ToString() + "——〉";
                results += "当前的计数:";
                results += MyCounter.GetCounter().ToString();
                results += "\n";

                Console.WriteLine(results);

                ///清空显示字符串
                results = "";
            }
        }

        public void StartMain()
        {

            Thread thread0 = Thread.CurrentThread; 

            thread0.Name = "Thread 0"; 

            Thread thread1 =new Thread(new ThreadStart(DoSomeWork)); 

            thread1.Name = "Thread 1"; 

            Thread thread2 =new Thread(new ThreadStart(DoSomeWork)); 

            thread2.Name = "Thread 2"; 

            Thread thread3 =new Thread(new ThreadStart(DoSomeWork)); 

            thread3.Name = "Thread 3"; 

            thread1.Start(); 

            thread2.Start(); 

            thread3.Start(); 

            ///线程0也只执行和其他线程相同的工作
            DoSomeWork(); 
       }
    }
}
using System; 
using System.Text; 
using System.Threading; 

namespace SigletonPattern.SigletonCounter 
{ 
    /// <summary> 
    /// 功能:实现多线程计数器的客户端 
    /// 编写:Terrylee 
    /// 日期:2005年12月06日 
    /// </summary> 
    public class CountClient 
    { 
       public static void Main(string[] args) 
       { 
       CountMutilThread cmt = new CountMutilThread(); 

            cmt.StartMain(); 

            Console.ReadLine(); 
        } 
    } 
}

总结

Singleton设计模式是一个非常有用的机制,可用于在面向对象的应用程序中提供单个访问点。文中通过五种实现方式的比较和一个完整的示例,完成了对Singleton模式的一个总结和探索。用一句广告词来概括Singleton模式就是“简约而不简单”。

需求是如何变成产品原型的-产品经理与交互设计师的对话

在一个互联网公司的工作流程中,产品经理(主要指偏向产品设计的产品人员)和交互设计师是这个流水线上最起点的环节,也是关系最暧昧的两个环节。说其暧昧,是因为在很多互联网公司里面,这两个环节所做的事情是有重合的,这就意味着,他们或许也是整个流程中合作最紧密的两个环节。

相对比之下,产品经理更关注的是产品的内部逻辑、操作流程、策略等;而交互设计师更关注的是产品的易用性、流畅度和操作感受。总的来看,似乎可以认为,产品经理是从一个更加宏观的角度去设计产品,而交互设计师,则是从更多的细节出发,去提升用户体验。这两种不同的视角决定了只有产品经理和交互设计师 密切配合,深入沟通,才能够最高效最合理的将产品策略转化为产品原型,从而为流水线的后面环节提供精确的参考资料。

下面以人人网广告平台的一些产品和交互细节为例,使用对话的形式来分享一下我个人在做交互设计过程中的一些体会和想法。入门级文章,高手请绕行。

在广告平台其中一个投放系统中,有一个产品需求,是要根据广告受众所在的地区做广告的定向投放。也就是说,可以控制广告只展示给固定地域的受众。这就意味着,需要设计一个「区域选择器」,供用户选择区域。产品经理的原始需求是这样的:

产品经理:“我们这次的投放系统需要设计一个区域选择器,就是让用户选择广告定向投放的区域的。可以精确到城市,可以多选。另外,‘区域’作为一个投放广告的限制条件,如果用户没有选择任何选项,那就代表用户忽略该条件,则该广告会面向全国投放。”

交互设计师:“哦。”

产品经理:“嗯,我能够想到的这个东西的原型,可以提供两个下拉框,让用户分别选择省和城市。当用户在第一个下拉框里面选定了省以后,第二个下拉框中会显示该省下面的地级市。我做了一个简单的框图,大家看一下。”

 

产品经理:“大概就是这个样子。每选定一个城市,点击后面的添加按钮,可以将该城市添加到下面的列表中。同时,如果点击已经添加的城市名称后面的「删除」链接,则会将该城市从已选列表抹去。”

交互设计师:“等一下,我有一个问题。按照产品的策略,如果用户一个城市都不选,那么就会默认投放全国。但是这个概念很难表达给用户,比如说,如果在「区域选择器」旁边放提示,估计没有多少人会注意到。”

产品经理:“一个都没选,就是等于忽略条件啊。因为这些都是限制条件。”

交互设计师:“问题是用户很难意识到是这样。在中国人的观念中,大家都是觉得,选上的,是我要的。但是大家没有「不选就等于全要」这种思维习惯。”

交互设计师:“我觉得可以这样。我们在现在的「区域选择器」上面放两个单选按钮。一 个叫「全国」,另一个叫做「指定」。打开页面时,默认选中「全国」项,并隐藏「区域选择器」。只有用户选择「指定」项时,区域选择器才会出现。这样表达就 很明确了,你不是「全国」就是「指定」。”

产品经理:“哦~听起来不错。试试。”

于是得到了下面这个版本的原型图:

 

交互设计师:“嗯,我想,现在这个版本已经基本上从界面的层面解决了全国投放的选项问题,我想,用户应该不会因为不知道怎么选而卡在这里了。”

交互设计师:“我看下一步,需要对一些关键的元素做一定的视觉设计,以便于引导用户操作。比如「添加」按钮,应该更明显些。我觉得可以请UI设计师出一个简单版本的UI了。”

产品经理:“稍等一下,我看咱们还是把细节再讨论清楚一些再去找UI吧。比如,字的颜色啊什么的都没定呢。而且,我觉得选中的区域中,每个城市名称后面都跟着一个删除链接,很奇怪。”

交互设计师:“嗯。的确。我的想法是这样,字的颜色,就用黑色吧,或者是深一些的灰 色也行。虽然从视觉设计的角度来看,视觉设计师觉得稍浅一些的灰色比较养眼,可能黑色太‘抢’。但是咱们的系统毕竟是给人用的,灰色的话,可能会让人误认 为这些选项是不可操作的。你看如何?”

产品经理:“同意。”

交互设计师:“关于已经选中的区域列表。我看可以把「删除」链接换成×,事实上,用户对于×这种符号比汉字更敏感。你看,大家不论是用Windows还是 Mac,关闭窗口的时候都是×,早就习惯了。另外,为了让所选定的城市名称看起来是一个整体,并且跟其他城市名称区分开。我看,可以给每一个城市加上背景 色,每个城市一个色块,这样也一目了然。”

产品经理:“颜色呢?”

交互设计师:“蓝色吧,人人网都是蓝色的风格。”

产品经理:“ok”

于是,配合UI设计师,得到了下面的界面:

 

产品经理:“我看现在这个地方已经基本上成型了。咱们也已经讨论很很久了。该问问别人的意见。”

———-时间分割线———-

产品经理:“Hi~ 各位。我收集了一些内部测试的意见。有用户提出,搞不太清楚两个下拉菜单和「添加」按钮之间的关系。”

交互设计师:“什么意思?”

产品经理:“就是说,有人意识不到选完了省,选完了城市以后,还得点「添加」。他们觉得,选完了就完事了。”

交互设计师:“晕。”

交互设计师:“可能是已选列表框在空着的时候长得太秀气了,大家没意识到它是用来装东西的。”

交互设计师:“好吧,我有两个方案。1、把「添加」按钮上面加一个向下的箭头。指向已选列表框。2、在已选列表空着的时候,添加一条提示语,来提示用户他并没有完成区域选择操作。”

产品经理:“提示语那个,你的意思是说,当用户添加了城市以后,会自动消失是吧?”

交互设计师:“是的。”

产品经理:“我觉得加提示吧。感觉放箭头有点儿傻。”

交互设计师:“嗯,而且,可能放了箭头以后,用户依然不知所云。”

产品经理:“那提示语怎么说呢?您尚未添加任何区域,请选定城市后,点击上面的「添加」按钮,该城市会被添加到…”

交互设计师:“停!太长了,大部分人不会认真看完的。”

产品经理:“的确…”

交互设计师:“我看就一句话就可以。写‘您尚未添加任何区域。’”

交互设计师:“你看。下拉列表后面的按钮叫「添加」。提示中又明确的传达出了尚未「添加」的状态。这样既说明了这个框框是用来放东西的,又可以告诉用户,这个东西是可以选多个的。因为「添加」的概念就是一个一个往里面放。如果只能放一个,那应该叫「选择」。”

产品经理:“顶。”

交互设计师:“而且,我觉得这个控件最初的原型你设计的不错。嗯,用户只要成功的进行一次操作,以后就可以非常高效的进行操作了。这个东西的学习成本和认知成本都比较低。”

产品经理:“oh,yeah~”

那么,现在的UI是这样的:

 

产品经理:“哎,对了。话说,我最开始的策略是,用户如果不选,相当于全选,要全国投放的。你说如果用户选了「指定」,但是并没有添加具体的城市,直接提交表单,怎么办?系统是应该直接把用户的广告设置成全国投放,还是报错,阻止用户继续?”

交互设计师:“我看啊,报错吧。因为现在「全国」和「指定」这两项,已经明确的把整 体和局部给分开了。我觉得你那个策略没必要再应用了,因为现在这种已经达到了你最初的目的,而且还好理解。再有就是,咱们的平台是涉及到钱的,是要让用户 花钱的,如果让用户不明不白花了冤枉钱,本来想投北京的投了全国,那我们会被用户骂死的。虽然感觉上报错会让用户有挫败感,但是在这种细节上,还是用户利 益应该放在第一位,用户体验,可以稍微往后放一放了。”

产品经理:“好吧。”

交互设计师:“呵呵,你看,这个故事告诉我们,不能每件事情都听产品的。产品提的只是需求,但是如何实现需求,还是得从多个角度来讨论。”

产品经理:“好吧。那么,技术兄弟们,下面的工作就拜托你们了。”

个人观点:

1、产品经理和交互设计师需要时刻密切配合,深入沟通。

2、有时候,产品策略和用户体验会发生冲突,这时应该从多种角度去考虑和探讨最终解决方案,不应该有谁一定比谁重要的说法。

3、优秀的产品经理,相当于半个交互。同样,优秀的交互设计师,相当于半个产品。二者虽然职位不同,但是应该在一定程度上考虑对方的工作内容。

4、产品提出的只是策略和需求,并不是一定要按照产品人员所描述的细节去设计具体的产品细节。交互设计师以及团队中其他所有成员,有义务有权利对产品需求提出自己的见解和更好的设计方案。有不同意见可以讨论,但是最终决定权,应该依然属于产品经理。

5、产品经理之所以叫经理,是因为,他们除了设计产品,还需要时刻把握整个流程。比如,当细节没讨论清楚的时候,不要去找UI做设计。

注:本文中对话部分并非真实情节,只是为了说明主题而已。

Java内部类的1些语法总结

最近在学习Android开发,对于Java也是从零开始,仅凭自己其它方面的开发经验。这是1篇来自互联网上的文章,Java的内部类还是很有意思的,C#也有咯。

从Java1.1开始引入了内部类以来,它就引起了人们的激烈争论。其实任何优秀的语言特性用得不好就是滥用,内部类用得不好就会导致代码像迷宫一样,导致出现毫无重用的综合征。

1、内部类分为成员内部类、静态嵌套类、方法内部类、匿名内部类。

几种内部类的共性:
A、内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类命和$符号。
B、内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的。

2、成员内部类:形式如下

class Outer {
class Inner{}
}

编译上述代码会产生两个文件:Outer.class和Outer$Inner.class。
成员内部类内不允许有任何静态声明!下面代码不能通过编译。

class Inner{
static int a = 10;
}

能够访问成员内部类的唯一途径就是通过外部类的对象!

A、从外部类的非静态方法中实例化内部类对象。

class Outer {
private int i = 10;
public void makeInner(){
Inner in = new Inner();
in.seeOuter();
}
class Inner{
public void seeOuter(){
System.out.print(i);
}
}
}

表面上,我们并没有创建外部类的对象就实例化了内部类对象,和上面的话矛盾。事实上,如果不创建外部类对象也就不可能调用makeInner()方法,所以到头来还是要创建外部类对象的。
你可能试图把makeInner()方法修饰为静态方法,即static public void makeInner()。这样不创建外部类就可以实例化外部类了!但是在一个静态方法里能访问非静态成员和方法吗?显然不能。它没有this引用。没能跳出那条规则!但是如果在这个静态方法中实例化一个外部类对象,再用这个对象实例化外部类呢?完全可以!也就是下一条的内容。

B、从外部类的静态方法中实例化内部类对象。

class Outer {
private int i = 10;
class Inner{
public void seeOuter(){
System.out.print(i);
}
} 
public static void main(String[] args) {
Outer out = new Outer();
Outer.Inner in = out.new Inner();
//Outer.Inner in = new Outer().new Inner();
in.seeOuter();
}
}

被注释掉的那行是它上面两行的合并形式,一条简洁的语句。
对比一下:在外部类的非静态方法中实例化内部类对象是普通的new方式:Inner in = new Inner();
在外部类的静态方法中实例化内部类对象,必须先创建外部类对象:Outer.Inner in = new Outer().new Inner();

C、内部类的this引用。
普通的类可以用this引用当前的对象,内部类也是如此。但是假若内部类想引用外部类当前的对象呢?用“外部类名”.this;的形式,如下例的Outer.this。

class Outer {
class Inner{
public void seeOuter(){
System.out.println(this);
System.out.println(Outer.this);
}
}
}

D、成员内部类的修饰符。
对于普通的类,可用的修饰符有final、abstract、strictfp、public和默认的包访问。
但是成员内部类更像一个成员变量和方法。
可用的修饰符有:final、abstract、public、private、protected、strictfp和static。
一旦用static修饰内部类,它就变成静态内部类了。

3、方法内部类。

顾名思义,把类放在方法内。

class Outer {
public void doSomething(){
class Inner{
public void seeOuter(){
}
} 
}
}

A、方法内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。

B、方法内部类对象不能使用该内部类所在方法的非final局部变量。
因为方法的局部变量位于栈上,只存在于该方法的生命期内。当一个方法结束,其栈结构被删除,局部变量成为历史。但是该方法结束之后,在方法内创建的内部类对象可能仍然存在于堆中!例如,如果对它的引用被传递到其他某些代码,并存储在一个成员变量内。正因为不能保证局部变量的存活期和方法内部类对象的一样长,所以内部类对象不能使用它们。
下面是完整的例子:

class Outer {
public void doSomething(){
final int a =10;
class Inner{
public void seeOuter(){
System.out.println(a);
}
} 
Inner in = new Inner();
in.seeOuter(); 
}
public static void main(String[] args) {
Outer out = new Outer();
out.doSomething();
}
}

C、方法内部类的修饰符。
与成员内部类不同,方法内部类更像一个局部变量。
可以用于修饰方法内部类的只有final和abstract。

D、静态方法内的方法内部类。
静态方法是没有this引用的,因此在静态方法内的内部类遭受同样的待遇,即:只能访问外部类的静态成员。

4、匿名内部类。

顾名思义,没有名字的内部类。表面上看起来它们似乎有名字,实际那不是它们的名字。

A、继承式的匿名内部类。

class Car {
public void drive(){
System.out.println("Driving a car!");
}
}
class Test{
public static void main(String[] args) {
Car car = new Car(){
public void drive(){
System.out.println("Driving another car!");
}
};
car.drive();
}
}

结果输出了:Driving another car! Car引用变量不是引用Car对象,而是Car匿名子类的对象。
建立匿名内部类的关键点是重写父类的一个或多个方法。再强调一下,是重写父类的方法,而不是创建新的方法。因为用父类的引用不可能调用父类本身没有的方法!创建新的方法是多余的。简言之,参考多态。

B、接口式的匿名内部类。

interface Vehicle {
public void drive();
}

class Test{
public static void main(String[] args) {
Vehicle v = new Vehicle(){
public void drive(){
System.out.println("Driving a car!");
}
};
v.drive();
}
}

上面的代码很怪,好像是在实例化一个接口。事实并非如此,接口式的匿名内部类是实现了一个接口的匿名类。而且只能实现一个接口。

C、参数式的匿名内部类。

class Bar{
void doStuff(Foo f){}
}

interface Foo{
void foo();
}

class Test{
static void go(){
Bar b = new Bar();
b.doStuff(new Foo(){
public void foo(){
System.out.println("foofy");
}
});
}
}

5、静态嵌套类。

从技术上讲,静态嵌套类不属于内部类。因为内部类与外部类共享一种特殊关系,更确切地说是对实例的共享关系。而静态嵌套类则没有上述关系。它只是位置在另一个类的内部,因此也被称为顶级嵌套类。
静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它。静态嵌套类不能访问外部类的成员和方法。

class Outer{
static class Inner{}
}
class Test {
public static void main(String[] args){
Outer.Inner n = new Outer.Inner();
}
}

一个老程序员的建议

本文是《Learn Python The Hard Way, 2nd Edition》这本书的尾声部分。

看完了这本书,你决定继续做编程。也许它能成为你的一个职业,也许它能成为你的一项爱好。但你需要一些指导,确保自己不会走错了道路,或帮助你从这个新业余爱好中得到最大的乐趣。

我做了很久的编程。久的你都想象不出来,久的都让我苦恼。就在我写这本书的时候,我大概懂20种编程语言,而且我可以用一天或长点儿用一周的时间学会一种新语言——要依这种语言有多奇怪而定。但这最终成为了我的苦恼,它们已经不能再吸引我的兴趣。我并不是说这些语言没有意思,或告诉你你会觉得它们很枯燥。只是想说在我的职业旅程走到现在,我已不再对语言有兴趣。

经过这么多年的学习经历,我发现语言本身并不重要,重要的是你如何用它们。事实上,我一直知道这个道理,但我总是被语言吸引走,周期性的忘记这个道理。现在我不再忘记了,你也应该这样。

你会什么语言、你用什么语言,这并不重要。不要被围绕在编程语言周围的各种宗教宣传迷惑,那些只会遮蔽你的眼睛,让你看不出这些语言只是一种让你做有趣的事情的工具而已。这才是它们的真正属性。

编程作为一种智力活动,它是唯一的一种能让你创造出交互式艺术作品的艺术形式。你创造出来人们可以操作的软件,你是在间接的和人们交互。没有任何其它艺术形式有如此的交互性。电影是单向的向观众传输信息。绘画是静态的。而软件程序却是双向动态的。

编程只能算是一项一般有趣的工作。它可以成为一个不错的职业,但如果你既想多挣钱又要干的高兴,不如去开一家快餐馆。如果你把编程当做一种秘密武器在其它行业里使用,也许会有更好的效果。

科技界科技公司里会编程的人多如牛毛,没人会在意他们。而在生物界,医药界,政府,社会学界,物理界,历史界和数学界,如果你有这种技能,你能做出令人瞩目的事情。

当然,所有的这些话都是没有意义的。如果通过这本书,你喜欢上了编程,你应该尽你最大的努力,通过它来改善你的生活。去探索这神奇的精彩的智力活动,也只有近50年来的人有机会从事这种职业。如果你喜欢它,就尽情的热爱它吧。

最后我要说的是,学习开发软件会改变你,让你与众不同。不论是好的不同还是坏的,反正是不同。你会发现,因为你会开发软件,人们会对你很冷淡,会用“书呆子”这样的词形容你。你会发现,由于你善于剖析逻辑,人们痛恨跟你辩论。你甚至会发现,只是简单的懂一些计算机原理都会给你带来很多烦恼,让你跟他们比起来怪怪的。

对于这些问题,我只有一点小建议:让他们去死吧。这世界需要更多的能知道事情如何工作、喜欢去探索的人。当他们这样对待你时,你要记住,这是你的生活,不是他们的。与众不同不是罪恶,人们这样对你只是出于嫉妒,嫉妒你拥有一项他们在梦中都无法拥有的技能。

你会编程。他们不会。这真他妈的酷毙了。

.NET之栈、堆、值类型、引用类型、装箱、拆箱

引言

这篇文章是我很早之前看到的,当时解决了我很多疑惑的地方。现在拿出来与大家分享。本篇文章主要介绍.NET中6个重要的概念:栈,堆,值类型,引用类型,装箱,拆箱。文章开始介绍当你声明一个变量时,编译器内部发生了什么,然后介绍两个重要的概念:栈和堆;最后介绍值类型和引用类型,并说明一些有关它们的重要原理。

最后通过一个简单的示例代码说明装箱拆箱带来的性能损耗。

声明变量的内部机制

在.NET程序中,当你声明一个变量,将在内存中分配一块内存。这块内存分为三部分:1,变量名;2,变量类型;3,变量值。

下图揭示了声明一个变量时的内部机制,其中分配的内存类型依据你的变量类型。.NET中有两种类型的内存:栈内存和堆内存。在接下来的内容中,我们会了解到这两种类型的详细内容。
Continue reading “.NET之栈、堆、值类型、引用类型、装箱、拆箱”